流是“从支持数据处理操作的源生成的一系列元素”。 和迭代器类似,流只能遍消费一次。
集合: 粗略地说,集合与流之间的差异就在于什么时候进行计算。集合是一个内存中的数据结构,它包含数据结构中目前所有的值——集合中的每个元素都得先算出来才能添加到集合中。集合遍历数据是外部迭代。流: 流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计 算的。流遍历数据是内部迭代。
分为中间操作和终端操作,也可分为有状态操作和无状态操作,如下图:
有状态操作: 诸如sort或distinct等操作一开始都和filter和map差不多——都是接受一个 流,再生成一个流(中间操作),但有一个关键的区别。从流中排序和删除重复项时都需要知 道先前的历史。无状态操作: 诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。
以下操作都用这个学生实体来演示:
static class Student { private int age; private String name; public Student(int age, String name) { this.age = age; this.name = name; } //...省略getter,setter方法 static Boolean isAdult(Student stu) { return stu.getAge() >= 16; } }匹配方法 以下三种匹配方法都用到了短路求值,且都是返回boolean。 anyMatch: 流中是否有一个元素能匹配给定的谓词; allMatch: 流中是否所有元素能匹配给定的谓词; noneMatch: 流中是否没有元素能匹配给定的谓词;
查找元素 findAny: 返回当前流中的任意元素
Optional<Student> stu = studentList.stream().filter(Student::isAdult).findAny();Optional类简介 Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。
isPresent()将在Optional包含值的时候返回true, 否则返回false。ifPresent(Consumer block)会在值存在的时候执行给定的代码块。我们在第3章 介绍了Consumer函数式接口;它让你传递一个接收T类型参数,并返回void的Lambda 表达式。T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。T orElse(T other)会在值存在时返回值,否则返回一个默认值。 当学生中有成年人的时候,将年龄设置为18 studentList.stream().filter(Student::isAdult).findAny().ifPresent(a -> a.setAge(18));findFirst: 返回找到的第一个元素。如果不关心返回的元素是哪个,请使用findAny,因 为它再使用并行流时限制较少。
元素求和
int sum = numbers.stream().reduce(0, (a,b) -> a + b); int sum1 = numbers.stream().reduce(0, Integer::sum); System.out.println(" sum1 = " + sum + "; sum1 = " + sum1);求最大值和最小值
Optional<Integer> max = numbers.stream().reduce(Integer::max); Optional<Integer> min = numbers.stream().reduce(Integer::min); max.ifPresent(System.out::println);计算流中的元素个数可以用map-reduce模式,也可以用count方法
int count = studentList.stream().map(d -> 1).reduce(0, (a,b) -> a + b); long count1 = studentList.stream().count(); System.out.println(count + " " + count1);使用流来对所有的元素并行求和时,你的代码几乎不用修改:stream()换成了parallelStream()。
int sum = studentList.parallelStream().reduce(0,Integer::sum);但要并行执行这段代码也要付一定代价:传递给reduce的Lambda不能更改状态(如实例变量),而且操作必须满足结合律才可以按任意顺序执行。
原始类型特化 Java 8引入了三个原始类型特化流接口来解决这个问题:IntStream、DoubleStream和 LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。
(1)映射到数值流
int ages = studentList.stream() .mapToInt(Student::getAge) .sum(); System.out.println(ages);(2)转换回对象流 要从数值流转回对象流才可以进行流的一些基本操作
IntStream intStream = studentList.stream().mapToInt(Student::getAge); Stream<Integer> stream = intStream.boxed();(3)默认值OptionalInt 对于三种原始流特化,也分别有一个Optional原始类型特化版本:OptionalInt、OptionalDouble和OptionalLong。
//求最大元素 OptionalInt maxAge = studentList.stream().mapToInt(Student::getAge).max(); System.out.println(maxAge.getAsInt());ps: reducing汇总和IntStream的sum汇总,后者更优,因为它最简明易读,也是性能最好的一个。
数值范围 range和rangeClosed方法都是第一个参数接收起始值,第二个参数接收结束值,range是半闭合的,不包括结束值。
IntStream intStream1 = IntStream.range(1,100); // 1-99由值创建流(Stream.of)
Stream<String> stream1 = Stream.of("java8", "Lambdas", "in", "action"); stream1.map(String::toUpperCase).forEach(System.out::println); //创建一个空流 Stream<String> emptyStream = Stream.empty();由数组创建流(Arrays.stream)
int[] numbers1 = {2, 3, 5, 7, 11, 13}; int sum2 = Arrays.stream(numbers1).sum();由文件生成流(Files.lines) 一个很有用的方法是Files.lines,它会返回一个由指定文件中的各行构成的字符串流。
long uniqueWords = 0; try(Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){ uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))) .distinct() .count(); }catch(IOException e){ }由函数生成流:创建无限流(Stream.iterate和Stream.generat) iterate方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator类型)。(迭代) generate不是依次对每个新生成的值应用函数的。它接受一个Supplier类型的Lambda提供新的值。 (生成)
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println); Stream.generate(Math::random).limit(5).forEach(System.out::println);(1)分组(groupingBy)
Map<Type, List<Student>> studentName = studentList.stream().collect(groupingBy(Student::getType)); studentName.forEach((k,v) -> System.out.println(v.toString()));多级分组(可以嵌套groupingBy)
// 先按是否成年来分组,再按新老生类型来分 Map<Type,Map<AgeType, List<Student>>> studentByTypeAndAgeType = studentList.stream().collect( groupingBy(Student::getType, groupingBy(student -> { if (student.getAge() < 18) {return AgeType.CHILD; }else { return AgeType.ADULT; } })) ); studentByTypeAndAgeType.forEach((k,v) -> System.out.println(v.toString()));输出: {CHILD=[name:LiPeng; age:12; type:oldStudent, name:wangfang; age:13; type:oldStudent, name:Lick; age:16; type:oldStudent], ADULT=[name:Lisi; age:18; type:oldStudent]} {CHILD=[name:zhangqiang; age:14; type:newStudent], ADULT=[name:John; age:20; type:newStudent]}
按子组收集数据 在groupingBy(f,toList())中,toList()是第二个收集器,可以是任何类型,而不一定是groupingBy,例如可以是counting(),maxBy()等一些函数。
//汇总新老生的分组数据 Map<Type,Long> countStudentByType = studentList.stream().collect( groupingBy(Student::getType,counting())); //查找新老生中年龄最大的学生 Map<Type,Optional<Student>> oldestStudentByType = studentList.stream().collect( groupingBy(Student::getType,maxBy(Comparator.comparing(Student::getAge)) )); //把收集器的结果转换为另一种类型 Map<Type,Student> oldestStudentByType1 = studentList.stream().collect( groupingBy(Student::getType, collectingAndThen(maxBy(Comparator.comparing(Student::getAge)), Optional::get) )); oldestStudentByType1.forEach((k,v) -> System.out.println(v.toString()));(2)分区(partioningBy) 分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函 数。分区的好处在于保留了分区函数返回true或false的两套流元素列表。
//按新老生分组 Map<Boolean, List<Student>> partitionedStudent = studentList.stream().collect( partitioningBy(Student::isAdult) ); List<Student> adultStudent = partitionedStudent.get(true); //求质数 Map<Boolean, List<Integer>> primeNum = IntStream.rangeClosed(2, 100).boxed() .collect( partitioningBy(candidate -> isPrime(candidate))); public static boolean isPrime(int candidate) { int candidateRoot = (int) Math.sqrt((double) candidate); return IntStream.rangeClosed(2, candidateRoot) .noneMatch(i -> candidate % i == 0); }拓展: isPrime方法是求质数的优化,为啥可以通过平方根来优化呢?因为如果它不是质数,那么它应该可以表示成两个非1非自身的数相乘。而这两个数,必然有一个大于平方根一个小于平方根,或者两个都等于平方根。
Stream未完待续,下一节将继续讲解收集器接口和并行~
转载于:https://www.cnblogs.com/pain-first/p/11536923.html