Java8 流式API

为什么引入流式API

在java中对于数据的操作常常是借助数据库来做到的,java自带的集合操作方法是不能对于大数据量的数据进行各种操作,常常要做的就是遍历再遍历,另外java中集合操作无法支持并行执行,流式API的出现可以发挥多核处理器的优势

使用

构建stream
  1. 静态工厂方法(Stream)

    1. of方法
      方法参数也可以将数组或者集合,转换为流,Java中流对象只有IntStream,DoubleStream和StringStream

      1
      IntStream intStream = of(1, 2, 3);
    2. generate方法

      该方法用于随机数产生,或者常量Stream等,返回无限长度的流最好借助lambda表达式

      1
      2
      Stream<Double> stream2 = Stream.generate(() -> Math.random());
      Stream<Double> stream3 = Stream.generate(Math::random);
    3. iterate方法

      1
      Stream.iterate(0, x -> x++);
    4. empty方法:返回一个一个空的流

      1
      Stream<Object> empty = Stream.empty();
  2. 集合和数组调用stream()方法

    stream的操作

流的操作分为两种:

  1. Intermediatemapfilterdistinctsortedpeeklimitskipparallelsequential
  2. TerminalforEachforEachOrderedtoArrayreducecollectminmaxcountiterator
    Intermediate中间操作不会导致流的消失,返回值为stream,Terminal操作结束后流即消失了
stream使用示例

demo数据如下:

1
2
3
4
5
6
7
8
9
10
List<User> list = new ArrayList<>();
list.add(new User("孙博文", 1));
list.add(new User("孙博文", 2));
list.add(new User("孙博文", 3));
list.add(new User("张三", 11));
list.add(new User("张三", 12));
list.add(new User("张三", 13));
list.add(new User("李文博", 21));
list.add(new User("李文博", 22));
list.add(new User("李文博", 23));

map:输入流中元素执行操作后一一映射输出流的元素,这个经常会用到,尤其是再将对象的集合转变为对象中属性的集合,另外map经常和reduce操作混合使用来归并数据

1
2
List<Integer> ageList1 = list.stream().map(x -> x.getAge()).collect(Collectors.toList());
List<Integer> ageList2 = list.stream().map(User::getAge).collect(Collectors.toList());

filter:条件为布尔值,将满足条件的过滤出来

1
List<User> collect = list.stream().filter(x -> x.getAge() > 0).collect(Collectors.toList());

distinct:去重,就可以看作放到set集合之中,但有序

1
list.stream().distinct().forEach(x-> System.out.println(x));

sorted:排序,基本数据类型是可以比较的(我没测全),对象直接比较会报错,需要自己实现比较器

1
2
3
4
5
6
7
8
9
List list1 = new ArrayList<>();
list1.add(3);
list1.add(1);
list1.add(4);
list1.stream().sorted().forEach(x -> System.out.println(x));

list.stream().map(User::getName).sorted().forEach(x -> System.out.println(x));

list.stream().sorted((x, y)->(x.getName().compareToIgnoreCase(y.getName()))).forEach(x -> System.out.println(x));

peek:用的不多,一般用来查看元素和打印元素,他和foreach的区别在于它只是中间操作,流依然存在

1
List<User> userList1 = list.stream().distinct().peek(x -> System.out.println(x)).collect(Collectors.toList());

limit/skip:截断流,区别在于limit截取前几个元素返回流,而skip是跳过前几个元素返回流

1
list.stream().limit(6).skip(3).forEach(x -> System.out.println(x.getName()));

parallel/sequential:并行流和串行流的互转,parallel将串行流转换为并行流,sequential将并行流转换为串行流,并行流不在乎保持最终的次序,所以更加快,但有时候顺序可能有误,串行流会维持最初的顺序,我用的不多


forEach:和peek类似只是为终端操作

forEachOrdered:foreach+sorted

toArray:归并流为数组
reduce:可以理解为将初始值,按照一定的规则,对每一个流中的元素迭代的执行,直至到最后,使用非常广泛,它的完整版接受三个参数:reduce(identity, accumulator, combiner),第一个参数为初始值,第二个为迭代操作,第三个为多个迭代结果的合并操作,另外两个方法都是其简略版,(accumulator)和(identity,accumulator),事实上min,max,count都是reduce操作,值得注意的是combiner的作用是将并行流的处理结果归并时的执行策略,我基本不用,也不说了,免得写错了

1
2
3
4
5
6
7
8
9
10
//求和(因为没有初始值,可能出现null,所以返回的optional)
Integer sum1 = list.stream().map(User::getAge).reduce(Integer::sum).get();
//完整版
Integer sum2 = list.stream().map(User::getAge).reduce(0, (x, y) -> y = y + x);
//求最小值
list.stream().map(User::getAge).reduce(Integer::min).get();
//求最大值
list.stream().map(User::getAge).reduce(Integer::max).get();
//字符串拼接
String s = list.stream().map(User::getName).reduce(String::concat).get();

max/min:如上所述,一种特殊的reduce操作,取出最大或者最小值,用法较简单

match:返回值为布尔类型,它包括有allMatch(全部满足返回true),anyMatch(任意一个满足返回true),noneMatch(没有一个满足返回true)

collect:它和reduce一样重要,将流的结果处理为定义的类型,继续介绍完整版方法collect(supplier, accumulator, combiner)

对于list而言,第一个为想要获取的结果类型,使用new语法获取对象;第二个为将当前元素添加到目标中的方法;第三个为将修改后的对象转为自定义对象方法;

1
2
3
4
//重载版
List<Integer> ageList1 = list.stream().map(x -> x.getAge()).collect(Collectors.toList());
//完整版
ArrayList<User> users = list.stream().collect(ArrayList<User>::new, ArrayList::add, ArrayList::addAll);

对于map集合而言,第一个参数为key的映射,第二个为value的映射,第三个为key相同时的处理策略

1
list.stream().collect(Collectors.toMap(User::getName, User::getAge, (K1, K2) -> K1));

进阶用法

直接分组
1
Map<String,List<User>>collect=list.stream().collect(Collectors.groupingBy(x->x.getName()));
按照姓名分组计数求和
1
2
3
4
5
        //这是个重载的方法
        Map<String, Long> collect1 = list.stream().
                collect(Collectors.groupingBy(x -> x.getName(), Collectors.counting()));
        Map<String, Integer> collect2 = list.stream()
                .collect(Collectors.groupingBy(x -> x.getName(), Collectors.summingInt(user -> user.getAge())));
collectAndThen迭代生成流的处理:它比前者要多出一个流处理位置
1
2
3
4
5
6
7
8
9
        //组内top值
        list.stream().
                collect(Collectors.groupingBy(User::getName,Collectors.maxBy(Comparator.comparingInt(User::getAge))))
                .forEach((name, maxAge) -> System.out.println("姓名:"+name+ "----""最大年龄" + maxAge.get().getAge()));
        list.stream()
                .collect(Collectors
                        .groupingBy(User::getName, Collectors
                                .collectingAndThen(Collectors.maxBy(Comparator.comparingInt(User::getAge)), x->x.get().getAge())))
                .forEach((name, maxAge) -> System.out.println("姓名:"+name+ "----""最大年龄" + maxAge));

相关

  1. Java 8 中的 Streams API 详解