JDK1.8新特性-Stream API

JAVA8中Stream是对集合对象功能的增强,它主要对集合对象进行高效聚合操作和大批量数据操作且StreamAPI支持Lambda表达式。同时提供串行和并行两种模式进行汇聚操作,能够写出高性能、简洁的多线程代码。

聚合操作

在日常开发中,大部分数据处理都是通过数据库中进行处理,然后取出到内存中使用业务代码进行处理,如:

  • 客户每月平均消费
  • 最昂贵的在售商品
  • 本周完成的有效订单
  • 取十个数据样本作为首页推荐

一些简单的业务逻辑需求可以直接使用RDBMS中进行处理,这样就不用返回到业务代码层进行处理,但是更为复杂的情况下,可能需要脱离RDBMS或者以数据库中返回的数据作为基础然后进一步处理。在JAVA7中我们使用for循环或者Iterator遍历,比较低效。

什么是Stream?

Stream不是集合元素,不是数据结构不保存数据,Stream类似迭代器,单向,不可重复,流数据只能遍历一次,同时Stream可以进行并发操作。

Stream的构成

使用流的步骤是:获取一个数据源➡数据转换➡执行操作获取想要的结果,每次转换原有Stream对象不变,返回一个新的Stream对象,允许对其操作可以像链条一样排列,变成一个管道。

Stream生成的方式

从Collection和数组

  • Collection.stream()
  • Collection.parallelStream()
  • Arrays.stream(T array)
  • Stream.of(Object obj...)

从BufferedReader

  • java.io.BufferedReader.lines()

静态工厂

  • java.util.stream.IntStream.range()

自己构建

  • java.util.Spliterator

其他

  • Random.ints()
  • BitSet.stream()
  • Pattern.splitAsStream(java.lang.CharSequence)
  • JarFile.stream()
  • Stream.generate
  • Stream.iterate

Stream的操作类型:

  • Intermediate:一个流后面可以接零或多个intermediate操作。目的是打开流,对数据进行映射和过滤,返回一个新的流,且这类操作是懒加载的,调用到该类方法时还没有开始流的遍历。能接收无限大Stream但返回有限的新Stream。
  • Terminal:一个流只能有一个terminal操作,当操作执行后,流就被使用完了,无法再被操作,所以terminal肯定是最后一个操作。Terminal操作的执行,才是真正开始流的遍历,并且生成一个结果。能接收一个无限大的Stream,但能在有限时间内计算出结果。
  • Short-circuiting:操作一个无限大Stream,要求在有限时间内完成操作,则在管道里拥有一个short-circuiting操作是必要非充分条件。

流的使用详解

常用的数值型Stream

IntStream、LongStream、DoubleStream

IntStream.of(new int[]{1,2,3}).forEach(System.out::println);
IntStream.range(1,3).forEach(System.out::println);
IntStream.rangeClosed(1,3).forEach(System.out::println);

流的操作

当一个数据元素转换成Stream,就需要对流进行操作了。

Intermediate:map(mapToInt、flatMap)、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered

Terminal:forEach、forEachOrdered、toArray、reduce、collect、min、max、cout、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator

Short-circuiting:anyMatch、allMatch、noneMatch、findFirst、findAny、limit

流操作方法详解

map/flatMap

map即映射,将Stream中的元素转换成另一种元素。map操作将数组中单个单词处理,去重,最后转换为集合返回,最后输出两个String数组。

String[] words = new String[]{"Hello","World"};
        List a = Arrays.stream(words)
                .map(word -> word.split(""))
                .distinct()
                .collect(toList());
        a.forEach(System.out::print);

flatMap也是映射,但会将Stream中的层级结构扁平化处理。将数组转换成流,使用flatMap对其中每个单词处理,将每个字符取出,然后去重,最后转换为集合返回,最后输出字符串集合。

String[] words = new String[]{"Hello","World"};
        List a = Arrays.stream(words)
                .flatMap(word -> word.split(""))
                .distinct()
                .collect(toList());
        a.forEach(System.out::print);

filter

对当前流中的元素进行条件过滤,条件为true的元素会被留下汇集成一个新的流。

 Integer[] sixNums = {1,2,3,4,5,6};
 Integer[] evens = Stream.of(sixNums).filter(n -> n>4).toArray(Integer[]::new);

以上filter操作,大于4的元素被留下,最终生成[5,6]的数组。

forEach

forEach方法是terminal操作,接收一个Lambda表达式,然后Stream上的每一个元素都执行forEach中的Lambda表达式。

Stream.of("1","2","3","4","5").forEach(System.out::print);

以上操作中forEach将流中所有元素都打印出来,且forEach没有返回值。forEach不能修改自己包含的本地变量值,也不能用break/return之类的关键字提前结束循环。。

peek

peek是Intermediate操作,与forEach的执行行为类似,它的存在主要是为了支持调试。对每个元素执行操作并返回一个新的Stream。

 Stream.of("1","2","3","4","5").peek(System.out::print).collect(toList());

findFirst

findFirst是terminal兼short-circuiting的方法,总是返回Stream的第一个元素或者空,它返回的是Optional。

Optional integer = Stream.of(1, 2, 3, 4, 5).findFirst();

reduce

reduce作用是将Stream中元素组合起来。它可以传入一个初始值,以初始值为基础,依次根据运算规则处理Stream中元素。例如Stream的sum就相当于

Integer sum = Stream.of(1,2,3,4,5).reduce(0,Integer::sum);

又或者reduce没有传入初始值,仅以此根据运算规则处理Stream中元素,最后返回Optional对象。

Optional optional = Stream.of(1,2,3,4,5).reduce(Integer::sum);

limit/skip

limit返回Stream前面的n的元素;skip是去除前面n个元素。

List limitList = Stream.of(1, 2, 3, 4, 5, 6).limit(3).collect(toList());
List skipList = Stream.of(1,2,3,4,5,6).skip(3).collect(toList());

sorted

对Stream的排序通过sorted进行,入参是比较大小的排序逻辑,它的优点就是可以在Stream被map、filter、limit后再排序。

List sortedList = Stream.of(1, 2, 3, 4, 5, 6).sorted(Integer::compare).collect(toList());

min/max/distinct

min:返回元素中最小的元素;

max:返回元素中最大的元素;

distinct:去除重复的元素,返回的元素在其列表中是唯一的。

Integer minInt = Stream.of(1, 2, 3, 4, 5, 6).min(Integer::compare).get();
Integer maxInt = Stream.of(1, 2, 3, 4, 5, 6).max(Integer::compare).get();
List disList = Stream.of(1, 1, 2, 3, 4, 5, 6,5).distinct().collect(toList());

allMatch/anyMatch/noneMatch

allMatch:入参为predicate,Stream中所有元素都符合逻辑判断最终才能返回true。

anyMatch:入参为predicate,Stream中所有元素仅需要一个元素符合逻辑判断最终才能返回true。

noneMatch:入参为predicate,需要Stream中所有元素都不符合逻辑判断最终才能返回true。

 boolean allMatch = Stream.of(1, 2, 3, 4, 5, 6).allMatch(x -> x > 3);
 boolean anyMatch = Stream.of(1, 2, 3, 4, 5, 6).anyMatch(x -> x > 3);
 boolean noneMatch = Stream.of(1, 2, 3, 4, 5, 6).noneMatch(x -> x > 3);

自己生成流

Stream.generate

通过Supplier接口,自己控制流的生成,通常用于随机数、常量的Stream或需要前后元素间维持着某种状态信息的Stream。把Supplier实例传递给Stream.generate()生成的Stream,默认是串行(相对parallel而言)但无序的(相对ordered而言)。由于它是无限的,在管道中,必须利用limit之类的操作限制Stream大小。

Random seed = new Random();
Supplier random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);

Stream.iterate

generate与reduce方式类似,有个初始值入参,然后以初始值为基础,传入的lambda表达式为逻辑操作进行迭代处理。如初始值是第一个元素,f(seed)为第二个元素,ff((seed))为第三个元素,以此类推。

Stream.iterate(0, n -> n + 3).limit(10).forEach(System.out::print);

用Collectors进行reduction操作

java.util.stream.Collectors类主要是辅助进行有用的reduction,如转变输出为Collection,将Stream元素进行归组。

groupingBy/partitioningBy

groupingBy以元素中的某个属性作为分组条件进行分组返回列表,如有映射关系的Map>。

Map> userGroups = Stream.generate(new UserSupplier()).limit(100).collect(Collectors.groupingBy(User::getAge));

partitioningBy

partitioningBy可以看作特殊的groupingBy,只根据true/false进行分组,返回类型是Map>,partitioningBy中入参是逻辑判断返回布尔值的lambda表达式。

Map> children = Stream.generate(new PersonSupplier()).limit(100).collect(Collectors.partitioningBy(p -> p.getAge() < 18));

Stream总结

  • 不是数据结构
  • 不能存储数据
  • 所有Stream操作必须以lambda表达式作为参数
  • 不修改底层数据
  • 懒加载,只有当terminal操作被执行,整个流的操作才会被执行
  • 可以进行多线程并行处理
  • 可以是无限的

参考文章

JDK1.8中的Stream详解_DoubleFJ の Blog-CSDN博客_jdk1.8 stream

你可能感兴趣的:(java,java,开发语言,后端)