JAVA - Stream

2021/4/10 12:57:27

本文主要是介绍JAVA - Stream,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、Stream简介

由来

Stream同样是Java8 中引入的一个新特性。它与传统的java.io中的Stream不同的是它并不用于字节传输,而是对集合、数组对象能力的增强。通过Stream操作可以把集合、数组中的对象进行过滤、分组、聚合、转换等复杂操作,把对Collection对象的复杂处理过程进行简化,减少代码的编写数量。

在大数据处理时代,巨量的数据处理会脱离RDBMS数据源。过去的Java API中少有对数据的批量处理方法,传统的解决方案都是在Iterator的迭代中完成数据加工。

Java7中要对年龄大于40的在职人员按工龄进行倒序排序后返回id,实现方法如下:

class EmployeeInfo {
    private String id;
    private String name;//姓名
    private int age;//年龄
    private int workingAge; //工龄
    private double income;//收入
    // ...更多属性
}
//在职员工列表
List<EmployeeInfo> employeeList = new Arraylist<>();
//过滤的列表
List<EmployeeInfo> filterList = new ArrayList<>();
for(EmployeeInfo e: employeeList){
 if(e.getAge() > 40){
 filterList.add(t);
 }
}
//排序
Collections.sort(filterList, new Comparator(){
 public int compare(EmployeeInfo t1, EmployeeInfo t2){
 return t2.getWorkingAge() - t1.getWorkingAge();
 }
});
//取id
List<String> employeeIds = new ArrayList<>();
for(EmployeeInfo e: filterList){
 employeeIds.add(e.getId());
}

Java8 中使用Stream的方式并使用并行计算可使代码简洁、高效

List<String> employeeIds = employeeList.stream()
    .filter(e->e.getAge() > 40)//过滤40岁以上
    .sorted(Comparator.comparing(EmployeeInfo::getWorkingAge).reversed())//工齡倒序
    .map(EmployeeInfo::getId)//提取id属性
    .collect(toList());//转换列表

介绍

Stream是具有一系列的数据处理方法的迭代器(类似Iterator),是数据的管道,本身并不负责存储数据,也不会对源数据产生任何影响,我们可以称之为,其特征正如流水

  • 单向迭代,一去不返
  • 可改道(sorted)、可分流(parallel)、可汇集(collect)
  • 可过滤(filter、distinct)、可开源(产生新流)、可截流(limit、skip、reduce)
  • 无穷无尽(无限流)

二、Stream的使用

Stream的创建

  • 从集合(Collection)或数组中产生

    Collection.stream()

    Collection.parallelStream()

    Arrays.stream(T array)

  • Stream静态方法

    Stream.of(…)

    Stream.generate(Supplier s) //无限生成

  • 其它API

    java.io.BufferedReader.lines()

    Random.ints()

Stream的操作

下面把Stream的操作进行完整分类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MNKoy59p-1618016771744)(https://i.loli.net/2020/07/09/i7hF3loQpI1RKHW.png)]

  • 中间操作(Intermediate operations)

    一个流可以后面跟随零个或多个 中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的,只有在指定的终结操作上才会执行中间操作。

    • 无状态操作

      处理流中的元素时,会对当前的元素进行单独处理。比如:过滤操作,因为每个元素都是被单独进行处理的,所有它和流中的其它元素无关,因此被称为无状态操作。

    • 有状态操作

      某个元素的处理可能依赖于其他元素。比如查找最小值,最大值,和排序,因为他们都依赖于其他的元素。因此为称为有状态操作。

  • 终结操作(Terminal operations)

    流只能有一个 终结操作,当这个操作执行后,流就被使用“光”了,无法再被操作,所以这必定是流的最后一个操作。终结操作的执行,才会真正开始流的遍历,并且会生成一个结果。

    • 非短路操作

      在完成Stream中所有元素迭代后才收集数据结果。

    • 短路操作

      返回boolean值,一般是操作一个无限的 Stream时,截断当前Stream,计算出结果。

  1. 筛选与切片(filter、distinct、limit、skip)

    filter方法:参数是一个Predicate类型的方法引用或Lambda表达式,对stream的内容进行筛选

    public void testFilter(){
        Stream<Integer> s = Stream.of(1,2,3,4,5,6);
        s.filter(a->a>3)//过滤大于3的数字
            .forEach(System.out::println);
        //输出4 5 6
    }
    

    distinct方法:无参数,对stream的内容进行去重筛选

    public void testDistinct(){
        Stream s = Stream.of(1,1,2,2,2,3,4,5,6,5,2,4);
        s.distinct().forEach(System.out::println);
        //输出1 2 3 4 5 6
    }
    

    limit方法:接收一个long类型的参数,从stream的头部保留对应数量的元素

    public void testLimit(){
        Stream<Integer> s = Stream.of(1,2,3,4,5,6);
        s.limit(4).forEach(System.out::println);
        //输出1 2 3 4
    }
    

    skip方法:接收一个long类型的参数,从stream的头部开始跳过对应数量,保留剩下的元素

    public void testSkip(){
        Stream<Integer> s = Stream.of(1,2,3,4,5,6);
        s.skip(4).forEach(System.out::println);
        //输出5 6
    }
    
  2. 收集(collect)

    因为Stream没有存储作用,Stream在方法调用完成后会被关闭,所以Stream类提供了collect方法,作用是将stream中的对象收集到集合中。

    收集到List

    public void collectList(){
    Stream<Integer> s = Stream.of(1,1,2,2,2,3,4,5,6,5,2,4);
    List<Integer> collect = s.collect(Collectors.toList());
    }
    

    收集到Set

    public void collectSet(){
     Stream<Integer> s = Stream.of(1,1,2,2,2,3,4,5,6,5,2,4);
     Set<Integer> collect = s.collect(Collectors.toSet());
    }
    

    收集到Map

    public void collectMap(){
    List<UserInfo> list = Arrays.asList(
      new UserInfo("张三","Zhang",23),
      new UserInfo("李四","Lee",24),
      new UserInfo("王五","King",25)
    );
    
    //以姓名为key的map
    Map<String, UserInfo> collect = list.stream()
      					.collect(Collectors.toMap(UserInfo::getName, Function.identity()));
        //Function.identity() 可以看作是t->t,代表返回对象本身
    }
    

    分组收集

    public void collectGroup(){
     Stream<Integer> s = Stream.of(1,1,2,2,2,3,4,5,6,5,2,4);
     //把相同的数字分到一组
     Map<Integer, List<Integer>> collect = s.collect(Collectors
                                                 .groupingBy(Function.identity(), Collectors.toList()));
     //{1=[1, 1], 2=[2, 2, 2, 2], 3=[3], 4=[4, 4], 5=[5, 5], 6=[6]}
    
     //按条件分割
     Map<Boolean, List<Integer>> partition = s.collect(Collectors.partitioningBy(a -> a > 3));
     //{false=[1, 1, 2, 2, 2, 3, 2], true=[4, 5, 6, 5, 4]}
    }
    
    class UserInfo {
     private String name;
     private String department;
     private int age;
    }
    
    //按部门(department)分组
    public void collectGroup(){
     List<UserInfo> list = Arrays.asList(
         new UserInfo("张三","dev",23),
         new UserInfo("李四","test",24),
         new UserInfo("王五","dev",25)
     );
    
     Map<String, List<UserInfo>> groupMap = list.stream().collect(Collectors
                                        .groupingBy(UserInfo::getDepartment,Collectors.toList()));
     /**
     {test=[TestCollect.UserInfo(name=李四, department=test, age=24)], dev=[TestCollect.UserInfo(name=张三, department=dev, age=23), TestCollect.UserInfo(name=王五, department=dev, age=25)]}
     */
    }
    

    统计

    public void sumnmary(){
     Stream<Integer> s = Stream.of(1,1,2,2,2,3,4,5,6,5,2,4);
     //最小值
     Optional<Integer> min = s.collect(Collectors.minBy(Integer::compareTo));
     //最大值
     Optional<Integer> max = s.collect(Collectors.maxBy(Integer::compareTo));
     //平均值
     Double d = s.collect(Collectors.averagingInt(a -> a));
     //总和
     Integer i = s.collect(Collectors.summingInt(a -> a));
    
     //专门的统计方法
     IntSummaryStatistics statistics = s.collect(Collectors.summarizingInt(Function.identity()));
     System.out.println(statistics.getSum());
     System.out.println(statistics.getAverage());
     System.out.println(statistics.getMax());
     System.out.println(statistics.getMin());
     System.out.println(statistics.getCount());
     //summarizingLong/summarizingDouble
    }
    

    拼接字符串

    public void join(){
     Stream<String> s = Stream.of("a","b","c","d","e");
     //String collect = s.collect(Collectors.joining(","));//a,b,c,d,e
    
     String collect = s.collect(Collectors.joining(",","[","]"));//[a,b,c,d,e]
    }
    
  3. 映射(map、flatmap)

    map方法:参数是一个Function类型的接口、方法引用或Lambda表达式,可把stream中的元素转换成另一种类型

    public void testMap(){
    List<UserInfo> list = Arrays.asList(
      new UserInfo("张三","Zhang",23),
      new UserInfo("李四","Lee",24),
      new UserInfo("王五","King",25)
    );
    //一对一 [用户信息,用户信息...] -> [姓名,姓名...]
    List<String> collect = list.stream()
      .map(UserInfo::getName)//把UserInfo对象映射成String对象
      .collect(Collectors.toList());
    }
    //一对多 [用户信息,用户信息...] -> [[姓名,昵称],[姓名,昵称]...]
    List<List<String>> collect2 = list.stream()
                 .map(u -> Arrays.asList(u.getName(), u.getNickname()))
                 .collect(Collectors.toList());
    

    flatmap方法:参数是一个返回类型为Stream的Function接口、方法引用或Lambda表达式,它可以将Stream中的元素统一抽取到一个新的Stream中

    List<List<Integer>> list = Stream.of(
                                     Arrays.asList(1),
                                     Arrays.asList(2,3),
                                     Arrays.asList(4,5)
                                 ).collect(Collectors.toList());
    //数据扁平化
    List<Integer> collect = list1.stream() //Stream<List<Integer>>
        .flatMap(List::stream)//Stream<Stream<Integer>>
        .collect(Collectors.toList());
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kaH7tw29-1618016771745)(https://i.loli.net/2020/07/09/k3oFETp5zilM4RY.png)]

  4. 合并(reduce)

    reduce方法:把当前Stream中的元素进行两两运算,生成一个只有一个值或对象的最终结果

    //求和操作
    Optional<Integer> sumo = Stream.of(1, 2, 3, 4)
                    .reduce(Integer::sum);
    
    //带初始值的求和操作
    Integer sum = Stream.of(1, 2, 3, 4)
                    .reduce(0,Integer::sum);
    
    

应用场景

字符统计

有本英文书要统计单词出现的次数

//单词
String str = "Java Stream Java Stream Stream SQL Java Stream API Java";
//单词计数
ConcurrentMap<String, Integer> collect = Arrays.stream(str.split(" "))
    .filter(word -> word.length() > 0)
    .collect(Collectors.toConcurrentMap(Function.identity(), w -> 1, Integer::sum));

行列转换

数据库中的存储是按年的行记录,前端报表需要将行记录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-27ua69jz-1618016771748)(https://i.loli.net/2020/07/09/VdfnuxWHyXeCa7k.png)]

输出JSON的数据结构为

[
    {year: 2019,population: 435543,populationf: 412042,populationl: 148768,populationw: 135665},
    {year: 2018,population: 413298,populationf: 382551,populationl: 137763,populationw: 123986},
    ......
]

数据要在前端进行图表展示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sOqGKGbm-1618016771749)(https://i.loli.net/2020/07/09/iJv9eI4LlnRfE5q.png)]

前面图表要求的JSONs数据结构为

[
    {year: 2019,x: '人口总数',y: 435543},
    {year: 2019,x: '农业人口总数',y: 412042},
    {year: 2019,x: '劳动力资源总数',y: 148768},
    {year: 2019,x: '从业人员数量',y: 135665},
    {year: 2018,x: '人口总数',y: 413298},
    {year: 2018,x: '农业人口总数',y: 382551},
    {year: 2018,x: '劳动力资源总数',y: 137763},
    {year: 2018,x: '从业人员数量',y: 123986},
]

Java代码

//数据库查询每年的记录
List<DataPopulationDTO> dataPopulationDTOS = baseDao.summaryPopulationByBeforeYear(year);

List<ChartData3> list = dataPopulationDTOS.stream()//Stream<DataPopulationDTO>
    .sorted(Comparator.comparing(DataPopulationDTO::getYear))//按年排序
    .map(d->{//行列转换,把行数据转列数据,每年的一条数据变成4条
        List<ChartData3> list = new ArrayList<>();
        ChartData3 data = new ChartData3(d.getYear(),"人口总数",d.getPopulation());
        ChartData3 data1 = new ChartData3(d.getYear(),"农业人口总数",d.getPopulationf());
        ChartData3 data2 = new ChartData3(d.getYear(),"劳动力资源总数",d.getPopulationl());
        ChartData3 data3 = new ChartData3(d.getYear(),"从业人员数量",d.getPopulationw());
        list.add(data);
        list.add(data1);
        list.add(data2);
        list.add(data3);
        return list;
    })//Stream<List<ChartData3>>
    .flatMap(List::stream)//数据扁平化,二维数据变一维  Stream<Stream<ChartData3>> - > Stream<ChartData3>
    .collect(Collectors.toList());//收集到List

三、Optional

Optional是Java 8引入的,用于表示可能存在的对象,引入的目的是尽可能地避免NullPointerException。

创建

Optional.of和Optional.ofNullable

//of方法接收一个非空的对象,如果对象是null会抛出异常
Optional<String> optional = Optional.of("str");

//如果不确定构建Optional的参数对象是否为空,可以用ofNullable方法
String str = null;
Optional<String> os = Optional.ofNullable(str);

使用

Optional<String> op = Optional.of("an Optional");

//与Stream一样,Optional接收一个Predicate函数,如果结果为false返回空的Optional
Optional<String> b = os.filter(s -> s.contains("d"));
//也可以调用map和flatmap方法

//返回Optional中的值,如果没有值会抛出NoSuchElementException异常
String value = op.get();

//判断Optional中是否有值
boolean isPresent = op.isPresent();

//判断如果有值的话做某些处理
op.ifPresent(System.out::println); //String::toUppercase

//判断如果没有值的话抛出异常处理
op.orElseThrow(()->{
   //记录日志
    log.error("值不能为空");
    //抛出异常
    return new RuntimeException("dfdf");
});

//当Optional的值不为空,返回值,当Optional为空时,返回orElse中的值
String svalue = op.orElse("some value");

//接收Lambda表达式或方法引用的API
String fvalue = op.orElseGet(()->"get from function");

orElse与orElseGet的区别

  • 参数

    orElse接收一个对象实例,orElseGet接收方法引用或Lambda表达式

  • 调用

    orElse不论Optional的值是否为空都会执行,orElseGet仅在Optional的值为空时才执行

总结

Stream的特性总结

  • 不能存储数据
  • 不能修改源数据
  • 不支持索引访问(下标)
  • 操作方法都是Lambda表达式
  • 中间操作都是惰性化的,只有终结操作时才会应用中间操作
  • 可以并行操作
  • 可以生成无限流



这篇关于JAVA - Stream的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程