本文最后更新于:May 13, 2023 pm
JAVA8 是一个有里程碑的一个版本,提供了很多的新特性。Java 8 是oracle公司于2014 年3月发布,可以看成是自Java 5 以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。
目录
预知
流的中间操作:如果Stream只有中间操作是不会执行的,当执行终端操作的时候才会执行中间操作,这种方式称为延迟加载或惰性求值。多个中间操作组成一个中间操作链,只有当执行终端操作的时候才会执行一遍中间操作链。
状态操作
无状态操作:所谓无状态就是一次操作,不能保存数据,线程安全
。如:filter、map、flatMap、peek 、unordered属于无状态操作。这些操作只是从输入流中获取每一个元素,并且在输出流中得到一个结果,元素和元素之间没有依赖关系,不需要存储数据。
有状态操作:所谓有状态就是有数据存储功能,线程不安全
。如:distinct、sorted、limit、skip属于有状态操作。这些操作需要先知道先前的历史数据,而且需要存储一些数据,元素之间有依赖关系。
创建流 流有两种形式,单列和双列。
单列流 主要是对于集合而言。
List<Integer> list = new ArrayList<>(); Stream<Integer> stream = list.stream();
数组流 主要是对于数组而言。有两种创建方式。
Integer[] a = {23 ,23 ,32 ,23 ,3 ,23 }; Stream<Integer> stream = Arrays.stream(a); Stream<Integer> a1 = Stream.of(a);
双列流 先转化为单列集合再创建流。
Map<String,Integer> map = new HashMap<>(); map.put("aaaa" ,13 ); map.put("bbbb" ,23 ); map.put("cccc" ,33 ); Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
中间操作 distinct(去重) 通过流所生成元素的 hashCode() 和 equals() 去除重复元素的Stream。
Stream<Stu> distinct = stus.stream().distinct();
filter(过滤) 对Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素。
Stream<Stu> stuStream = stus.stream().filter(it -> it.getAge() > 20 );
括号内为过滤条件。
sorted(排序) 通过比较器指定比较规则进行排序。
Stream<Stu> sorted = stus.stream().sorted(Comparator.comparing(Stu::getName)); Stream<Stu> sorted = stus.stream().sorted(Comparator.comparing(Stu::getName, Comparator.reverseOrder()));
limit(截断) 截断流,使其元素不超过给定数量。如果元素的个数小于maxSize,那就获取所有元素。
Stream<Stu> limit = stus.stream().limit(20 );
skip(跳过) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补。
Stream<Stu> skip = stus.stream().skip(3 );
map(转换流) 转换流,将原Stream转换成一个新的Stream。接收一个Function函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
map方法有3个扩展方法:
mapToInt:转换成IntStream。
mapToLong:转换成LongStream。
mapToDouble:转换成DoubleStream。
Stream<Integer> integerStream = stus.stream().map(Stu::getAge);
flatMap(转换流并合并) 转换流,将原Stream中的每个元素都转换成另一个Stream,然后把所有Stream连接成一个新的Stream。接收一个Function函数作为参数。
class Stu { String name; List<String> roles = new ArrayList<>(); }
测试:
List<Stu> lists = new ArrayList<>(); Stu a = new Stu(); a.setName("aaaaaaa" ); a.setRoles(Arrays.asList("admin" ,"user" ,"root" )); Stu b = new Stu(); b.setName("bbbbbbb" ); b.setRoles(Arrays.asList("qwer" ,"admin" ,"root" )); lists.add(a); lists.add(b); List<String> collect = lists.stream().flatMap(it -> it.getRoles().stream()).collect(Collectors.toList()); collect.forEach(System.out::println);
输出:
admin user root qwer admin root
peek(打印或修改) 生成一个包含源stream所有元素的新stream,同时会提供一个消费函数(consumer实例),新stream每个元素被消费的时候都会执行给定的消费函数。
peek主要被用在debug用途。可用于打印某中间操作
后得到的元素信息。
class Stu { String name; int age; long weight; double money; }
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 List<Stu> stus = new ArrayList<>(); Stu s1 = new Stu("bbbbb" ,12 ,24 ,23.24 ); Stu s2 = new Stu("ddddd" ,1 ,124 ,133.124 ); Stu s3 = new Stu("aaaaa" ,13 ,34 ,13.14 ); Stu s4 = new Stu("ccccc" ,17 ,23 ,113.114 ); Stu s5 = new Stu("eeeee" ,123 ,341 ,135.134 ); stus.add(s1); stus.add(s2); stus.add(s3); stus.add(s4); stus.add(s5); List<Stu> collect = stus.stream() .peek(it -> System.out.println(it.getName() + " 的年龄为:" )) .filter(it -> it.getAge() > 10 ) .peek(it -> System.out.println(it.getAge() + " 大于10 " )) .filter(it -> it.getAge() > 20 ) .peek(it -> System.out.println(it.getAge() + " 大于20 " )).collect(Collectors.toList()); collect.forEach(System.out::println);
输出:
bbbbb 的年龄为:12 大于10 ddddd 的年龄为: aaaaa 的年龄为:13 大于10 ccccc 的年龄为:17 大于10 eeeee 的年龄为:123 大于10 123 大于20 Stu{name='eeeee' , age=123 , weight=341 , money=135.134 }
这里可以看出,操作顺序是:每个元素完成所有中间操作后,下一个元素才开始完成所有中间操作。简单来说就是处理元素是一个一个的来的,上个元素完成后,下个元素才继续。
parallel(并行流) 将流转化为并行流。
流的并行操作的线程数默认是机器的CPU个数。默认使用的线程池是ForkJoinPool.commonPool线程池。
sequential(串行流) 将流转化为串行流。
多次调用parallel()和sequential(),以最后一次为准。
终止操作 forEach(遍历) 串行流的forEach会按顺序执行,但并行流的时候,并不会按顺序执行。
stus.stream().forEach(it-> System.out.println(it));
forEachOrdered(顺序遍历) 确保按照原始流的顺序执行(即使是并行流)。
stus.stream().parallel().forEachOrdered(it-> System.out.println(it));
toArray(转数组) 转化为数组。
toArray():将流转换成适当类型的数组。
toArray(generator):用于分配自定义的数组存储。
Object[] objects = stus.stream().toArray(); Stu[] stus1 = stus.stream().toArray(Stu[]::new );
findFirst(首元素) 返回一个含有第一个流元素的Optional类型的对象,如果流为空返回 Optional.empty。
findFirst() 无论流是串行流还是并行流,总是会选择流中的第一个元素(有顺序的)。
Optional<Integer> first = stus.stream().map(it -> it.getAge()).findFirst(); Optional<Integer> firstP = stus.stream().parallel().map(it -> it.getAge()).findFirst(); System.out.println(first.get()); System.out.println(firstP.get());
findAny(任意元素) 返回含有任意一个流元素的Optional类型的对象,如果流为空返回 Optional.empty。
对于串行流,findAny()和findFirst() 效果相同,选择流中的第一个元素。 对于并行流,findAny()也会选择流中的第一个元素(但因为是并行流,从定义上来看是选择任意元素)。
Optional<Integer> firstP = stus.stream().parallel().map(it -> it.getAge()).findAny(); Optional<Integer> first = stus.stream().map(it -> it.getAge()).findAny(); System.out.println(first.get()); System.out.println(firstP.get());
⚠️注意:自己在测试时,发现:如果将并行流放在串行流下面,那么两个的结果就是定了的(也许是我的问题,但我跑了很多遍发现都是一样的)。只有是上述代码才能保持每一次的结果都能让并行流的结果不一样。
allMatch(全部匹配) Stream中全部元素符合传入的Predicate,返回 true,只要有一个不满足就返回false。
boolean b = stus.stream().allMatch(it -> it.getAge() > 0 );
anyMatch(任意匹配) Stream中只要有一个元素符合传入的 Predicate,返回 true。否则返回false。
boolean b = stus.stream().anyMatch(it -> it.getAge() > 100 );
noneMatch(不匹配) Stream中没有一个元素符合传入的 Predicate(即全部不满足条件),返回 true。只要有一个满足就返回false。
boolean b = stus.stream().noneMatch(it -> it.getAge() < 0 );
reduce(组合流元素) 把Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。字符串拼接、sum、min、max、average 都是特殊的reduce。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Integer reduce = stus.stream().map(it -> it.getAge()).reduce(0 , (a1, a2) -> a1 + a2); System.out.println(reduce); Integer reduce = stus.stream().map(it -> it.getAge()).reduce(1 , (a1, a2) -> a1 + a2); System.out.println(reduce); Integer reduce = stus.stream().map(it -> it.getAge()).parallel().reduce(0 , (a1, a2) -> a1 + a2); System.out.println(reduce); Integer reduce = stus.stream().map(it -> it.getAge()).parallel().reduce(1 , (a1, a2) -> a1 + a2); System.out.println(reduce); Integer reduce = stus.stream().map(it -> it.getAge()).reduce(0 , (a1, a2) -> a1 * a2); System.out.println(reduce); Integer reduce = stus.stream().map(it -> it.getAge()).reduce(1 , (a1, a2) -> a1 * a2); System.out.println(reduce); Integer reduce = stus.stream().map(it -> it.getAge()).parallel().reduce(0 , (a1, a2) -> a1 * a2); System.out.println(reduce); Integer reduce = stus.stream().map(it -> it.getAge()).parallel().reduce(1 , (a1, a2) -> a1 * a2); System.out.println(reduce);
总结
串行流对总结果造成影响;并行流对每一个元素造成影响。
串行流:初始值+值1+值2+值3…;并行流:(初始值+值1)+(初始值+值2)+(初始值+值3)…
max 求元素的最大值,如果流为空返回Optional.empty。示例见min示例。⚠️注意和min时的用法区别。
min 求元素的最小值,如果流为空返回Optional.empty。⚠️注意和max的使用区别。
List<Stu> stus = new ArrayList<>(); Stu s1 = new Stu("bbbbb" , 23 , 24 , 23.24 ); Stu s2 = new Stu("ddddd" , 11 , 124 , 133.124 ); Stu s3 = new Stu("aaaaa" , 43 , 34 , 13.14 ); Stu s4 = new Stu("aaaaa" , 13 , 34 , 13.14 ); Stu s5 = new Stu("aaaaa" , 33 , 34 , 13.14 ); stus.add(s1); stus.add(s2); stus.add(s3); stus.add(s4); stus.add(s5); Optional<Stu> max = stus.stream().max(((o1, o2) -> o1.getAge() - o2.getAge())); System.out.println(max.get()); Optional<Stu> min = stus.stream().min(((o1, o2) -> o1.getAge() - o2.getAge())); System.out.println(min.get());
输出:
Stu{name='aaaaa' , age=43 , weight=34 , money=13.14 } Stu{name='ddddd' , age=11 , weight=124 , money=133.124 }
可以看见,不管对于max和min来说,都是使用了同样的比较器。但是结果求不一样。此比较器在正常的数组或集合中进行排序时,为默认升序排序。而在这里,相对于max来说,默认就是求最大值;相对于min来说,默认就是求最小值。所以,同一比较器对于不同的方法具有不同功能。(至少我自己时这样理解的)
count 流中的元素个数。
sum 对所有流元素进行求和。
stus.stream().mapToInt(it -> it.getAge()).sum()
average 求取流元素平均值。
stus.stream().mapToInt(Stu::getAge).average()
collect(收集) 收集流元素转换成最后需要的结果。使用Collector 收集流元素到结果集中,主要是使用 Collectors(java.util.stream.Collectors)。具体的使用方式见高级篇中。
Collectors的方法如下:
方法名
描述
toList
将流中的元素放置到一个列表集合中。这个列表默认为ArrayList。
toSet
将流中的元素放置到一个无序集set中。默认为HashSet。
toCollection
将流中的元素全部放置到一个集合中,这里使用Collection,泛指多种集合。
toMap
根据给定的键生成器和值生成器生成的键和值保存到一个map中返回,键和值的生成都依赖于元素,可以指定出现重复键时的处理方案和保存结果的map。
toConcurrentMap
与toMap基本一致,只是它最后使用的map是并发Map:ConcurrentHashMap
joining
将流中的元素全部以字符序列的方式连接到一起,可以指定连接符,甚至是结果的前后缀。内部拼接使用的Java8的新类StringJoiner,可定义连接符和前缀后缀。
mapping
先对流中的每个元素进行映射,即类型转换,然后再将新元素以给定的Collector进行归纳。类似Stream.toMap。
collectingAndThen
在归纳动作结束之后,对归纳的结果进行再处理。
counting
元素计数
minBy
最小值的Optional
maxBy
最大值的Optional
summingInt
求和,结果类型为int
summingLong
求和,结果类型为long
summingDouble
求和,结果类型为double
averagingInt
平均值,结果类型int
averagingLong
平均值,结果类型long
averagingDouble
平均值,结果类型double
summarizingInt
汇总,结果包含元素个数、最大值、最小值、求和值、平均值,值类型都为int
summarizingLong
汇总,结果包含元素个数、最大值、最小值、求和值、平均值,值类型都为long
summarizingDouble
汇总,结果包含元素个数、最大值、最小值、求和值、平均值,值类型都为
reducing
统计,reducing方法有三个重载方法,其实是和Stream里的三个reduce方法对应的,二者是可以替换使用的,作用完全一致,也是对流中的元素做统计归纳作用。求和、平均值、最大值、最小值等其实就属于一种特殊的统计
groupingBy
分组,得到一个HashMap
groupingByConcurrent
分组,得到一个ConcurrentHashMap
partitioningBy
将流中的元素按照给定的校验规则的结果分为两个部分,放到一个map中返回,map的键是Boolean类型,值为元素的列表List
使用示例:
数据:
List<Stu> stus = new ArrayList<>(); Stu s1 = new Stu("bbbbb" , 23 , 24 , 23.24 ); Stu s2 = new Stu("ddddd" , 11 , 124 , 133.124 ); Stu s3 = new Stu("aaaaa" , 43 , 34 , 13.14 ); Stu s4 = new Stu("eeeee" , 13 , 34 , 13.14 ); Stu s5 = new Stu("ccccc" , 33 , 34 , 13.14 ); stus.add(s1); stus.add(s2); stus.add(s3); stus.add(s4); stus.add(s5);
测试:
List<Stu> collect = stus.stream().collect(Collectors.toList()); Set<Stu> collect = stus.stream().collect(Collectors.toSet()); LinkedList<Stu> collect = stus.stream().collect(Collectors.toCollection(LinkedList::new ));
toMap(转换为Map) 有三种重载函数,两个参数、三个参数、四个参数。
Map<String, Integer> collect = stus.stream().collect(Collectors.toMap(it -> it.getName(), it -> it.getAge())); Map<String, Integer> collect = stus.stream().collect(Collectors.toMap(it -> it.getName(), it -> it.getAge(),(k1,k2)->k1)); Map<String, Integer> collect = stus.stream().collect(Collectors.toMap(it -> it.getName(), it -> it.getAge(), (k1, k2) -> k1, TreeMap::new ));
⚠️提示:上述方法同样适用于 toConcurrentMap,大同小异。
joining(拼接) 有三种重载函数,无参、一个参数、三个参数。
String collect1 = stus.stream().map(Stu::getName).collect(Collectors.joining()); String collect2 = stus.stream().map(Stu::getName).collect(Collectors.joining("[[" )); String collect3 = stus.stream().map(Stu::getName).collect(Collectors.joining("\",\"" , "[\"" , "\"]" )); System.out.println(collect1); System.out.println(collect2); System.out.println(collect3); bbbbbdddddaaaaaeeeeeccccc bbbbb[[ddddd[[aaaaa[[eeeee[[ccccc ["bbbbb" ,"ddddd" ,"aaaaa" ,"eeeee" ,"ccccc" ]
无参:将字符串流直接进行拼接操作。
一个参数(delimiter):自定义分隔符。实际上是调用了三个参数的方法,只是后两个参数传入的是null。
三个参数(delimiter、prefix、suffix):delimiter表示分隔符,prefix表示第一个字符串前面的前缀,suffix表示最后一个串后的后缀。
mapping 将某值映射转换为另外一种形式。如:int -> String;String -> int。
List<String> collect1 = stus.stream().map(Stu::getAge).collect(Collectors.mapping(String::valueOf, Collectors.toList())); collect1.forEach(System.out::println); List<Integer> collect = Stream.of("1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" , "10" ).collect(Collectors.mapping(Integer::valueOf, Collectors.toList())); collect.forEach(System.out::println);
在IDEA的提示下有另外一种方式:
stus.stream().map(Stu::getAge).map(String::valueOf).collect(Collectors.toList()); List<Integer> collect = Stream.of("1" , "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" , "10" ).map(Integer::valueOf).collect(Collectors.toList());
其他方法 concat(合并流) 合并两个流成一个新的流。
Stream<Stu> concat = Stream.concat(stus.stream(), sss.stream());