stream详解

714 阅读7分钟

 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作 。Stream API 可以借助于新出现的Lambda表达式,极大的提高编程效率和程序可读性。

stream构成

获取一个数据源(source)→ 数据转换 → 执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换)。

stream的操作类型

中间操作

一个流后面可以跟随0个或多个中间操作。其目的是打开流,做出某种程度的数据映射、过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是懒惰的(lazy ),仅仅调用到这类方法,并没有真正开始流的遍历。

终止操作

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

stream使用

获取stream流

就写常用的吧,不常用的一般还真不用

  • Collection.stream(); 

  • Collection.parallelStream();

  • Arrays.stream();

  • stream.of(T t);    

    List list = Arrays.asList(1, 2, 3); Stream stream = list.stream(); Stream stream = list.parallelStream();

    Integer[] nums = new Integer[] {1, 2, 3, 4, 5}; Stream stream = Arrays.stream(nums); Stream stream = Stream.of(nums);

对于基本数值型,目前提供了三种对应的包装类型 Stream

IntStream、LongStream、DoubleStream

Map/flatMap(映射)

把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素。map 生成的是个 1:1 映射。flatMap是一对多映射关系的。

Collection<Student> students = Arrays.asList(        
    new Student(1, Grade.FIRST, 60),        
    new Student(2, Grade.SECOND, 80),        
    new Student(3, Grade.FIRST, 100)
);

map

students.stream().map(Student::getScore).collect(Collectors.toList());

可以获取到id数组  [60, 80, 100]

students.stream().map(item -> item.getId() + " " + item.getScore()).collect(Collectors.joining(","));

返回值为1 60,2 80,3 100

flatMap

Stream<List<Integer>> flatMapStream = Stream.of(        
    Arrays.asList(1, 2, 3),        
    Arrays.asList(4, 5, 6),        
    Arrays.asList(7, 8, 9, 10)
);

先执行map,结果为3 3 4,为流内部list的长度

flatMapStream.map(List::size).forEach(item -> System.out.print(item + " "));

在执行floatMap,结果为1 2 3 4 5 6 7 8 9 10

flatMapStream.flatMap(Collection::stream).forEach(item -> System.out.print(item + " "));

Map是把Stream中的每个List当做管道中的元素。flatMap是把Stream中的每个List的子元素当做管道中的元素

flatMapStream.map(List::size).forEach(item -> System.out.print(item + " "));
flatMapStream.flatMap(Collection::stream).forEach(item -> System.out.print(item + " "));

当代码中同时出现对一个流的操作时,会出现什么问题?

java.lang.IllegalStateException: stream has already been operated upon or closed

每次创建的stream只能使用一次

Map、flatMap的区别

map(Function<? super T, ? extends R> mapper)
flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); 
interface Function<T, R> {R apply(T t);}

第一个参数是一样的,但是第二个不一样,flatMap要求返回的是继承stream类型的类型。也就是说函数体只能返回一个stream类型的流。

Reduce

把 Stream 元素组合起来。它提供一个起始值,然后依照运算规则和Stream的每一个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。

Integer[] nums = new Integer[]{1,2,3,4,5,6};
Integer sum = Stream.of(nums).reduce(0, (n1, n2) -> n1 + n2);

第一个为起始值,第二个为运算规则。没有起始值时返回的是Optional。需要get()获取。

filter(过滤)

用于通过设置的条件过滤出元素。

传递的是interface Predicate,返回值为boolean test(T t),是否满足

Integer[] nums = new Integer[]{1,2,3,4,5,6};
Stream.of(nums).filter(item -> item > 3).collect(Collectors.toList());

返回值为[4, 5, 6]。

forEach(循环)终止操作

迭代流中的每个数据。forEach是一个终止操作。无法对一个流进行两次终止操作

Integer[] nums = new Integer[]{1,2,3,4,5,6};
Stream.of(nums).filter(item -> item > 3).forEach(System.out::print);

返回值为456

void forEach(Consumer<? super T> action);

返回值为void,不会返回流,终止操作

peek(循环)中间操作

Integer[] nums = new Integer[]{1,2,3,4,5,6};
Stream.of(nums).filter(item -> item > 3)        
    .peek(item -> System.out.print(item + 1))        
    .peek(System.out::print)        
    .reduce(0, Integer::sum);

可以多次peek操作。注意:结果为546576。 这个结果不好解释,自己体会吧。

第一次的peek操作修改数据不会对第二次的peek数据造成影响。因为peek传递的是Consumer类型的函数体,只能消费,不能返回

Stream<T> peek(Consumer<? super T> action);

会返回流

Limit/skip

limit 返回 Stream 的前面 n 个元素;skip 则是跳过前 n 个元素

Integer[] nums = new Integer[]{1,2,3,4,5,6};
Stream.of(nums).limit(4).skip(2).forEach(System.out::print);

去前四个元素并跳过前两个。返回值为34

sorted(排序)

对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、 limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间

Integer[] nums = new Integer[]{4,2,2,4,3,5,6};
Stream.of(nums).sorted((x, y)->x.compareTo(y)).forEach(System.out::print);

返回结果2234456

distinct(去重)/min/max

Integer[] nums = new Integer[]{4,2,2,4,3,5,6};
Stream.of(nums).distinct().forEach(System.out::print); //42356
System.out.println(Stream.of(nums).min(Comparator.naturalOrder()).get()); // 2
System.out.println(Stream.of(nums).max(Comparator.naturalOrder()).get()); // 6

Match(匹配)

Stream 有三个 match 方法,从语义上说: 

  • allMatch:Stream 中全部元素符合传入的 predicate,返回 true 

  • anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true 

  • noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true

    Integer[] nums = new Integer[]{4,2,2,4,3,5,6}; System.out.println(Stream.of(nums).anyMatch(x -> x>5)); // true

groupingBy/partitioningBy(分组)

Collection<Student> students = Arrays.asList(        
    new Student(1, Grade.FIRST, 60),        
    new Student(2, Grade.SECOND, 80),        
    new Student(3, Grade.FIRST, 100)
);

groupingBy

对上面的Student进行按年级进行分组

students.stream().collect(Collectors.groupingBy(Student::getGrade))
    .forEach((grade, list) ->{    
        System.out.print(grade + " -> ");    
        list.forEach(s-> System.out.print("{" + s.getId() + "," + s.getGrade() + "," + s.getScore() + "}"));    
        System.out.println();
});

返回值为 SECOND -> {2,SECOND,80} FIRST -> {1,FIRST,60}{3,FIRST,100}

partitioningBy

对上面的Student进行按成绩进行分组

students.stream().collect(Collectors.partitioningBy(student -> student.getScore() > 60))
    .forEach((grade, list) ->{    
        System.out.print(grade + " -> ");    
        list.forEach(s-> System.out.print("{" + s.getId() + "," + s.getGrade() + "," + s.getScore() + "}"));    
        System.out.println();
});

返回值为 false -> {1,FIRST,60} true -> {2,SECOND,80}{3,FIRST,100}

parallelStream

parallelStream其实就是一个并行执行的流.其底层使用Fork/Join框架实现,可以提高你的多线程任务的速度。

可以通过parallel()把stream转换为并行执行流。也可以通过parallelStream()获取。

Integer[] nums = new Integer[] {1, 2, 6,3,2,1,4,2,5};
Arrays.stream(nums).forEach(System.out::print); // 126321425
System.out.println();
Arrays.stream(nums).parallel().forEach(System.out::print); // 122546321

注意:

使用并行执行流可以提高多线程任务速度,但是也会改变数组的遍历顺序(仅从上面例子上发现)。

parallelStream适合没有线程安全问题。

我个人用的不多,就不仔细解释了。

parallelStream底层是使用的ForkJoin。而ForkJoin里面的线程是通过ForkJoinPool来运行的,Java 8为 ForkJoinPool添加了一个通用线程池,这个线程池用来处理那些没有被显式提交到任何线程池的任务。它是 ForkJoinPool类型上的一个静态元素。它拥有的默认线程数量等于运行计算机上的处理器数量,所以这里就出现 了这个java进程里所有使用parallelStream的地方实际上是公用的同一个ForkJoinPool。parallelStream提供了更 简单的并发执行的实现,但并不意味着更高的性能,它是使用要根据具体的应用场景。如果cpu资源紧张 parallelStream不会带来性能提升;如果存在频繁的线程切换反而会降低性能。 

Collect(流转换为其他数据结构)

转换为list

Stream<String> nums = Stream.of(new String[]{"1", "2", "3", "4", "5"});
List<String> list = nums.collect(Collectors.toList());

也可以这样写

List<String> list = nums.collect(Collectors.toCollection(ArrayList::new));

转换为字符串

String str =nums.collect(Collectors.joining());

总结

  • stream不是数据结构,它没有内部存储,它只是用操作管道从source抓取数据 
  • Stream的操作会产生一个新Stream,而不是从source修改后得到
  • 所有 Stream 的操作必须以 lambda 表达式为参数 
  • 惰性化,很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始,中间操作永远是惰性化的。
  • 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。