Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
Stream 用来操作集合或数组,但不会改变集合或数组,也就是说流操作的数据源不会受影响,Stream 相当于在数据源和操作后的新流之间搭起一个数据传输管道,在这个传输管道中通过流计算进行一系列流水线式的中间操作,产生一个新的流。
也就是说 Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”。
注意:
①Stream 自己不会存储元素
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream
③Stream 操作是延迟执行的,这意味着他们会等到需要结果的时候才执行
1、创建 Stream:一个数据源(如:集合、数组),获取一个流
2、中间操作:一个中间操作链,对数据源的数据进行处理
3、终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果
1、通过Collection系列集合提供的stream()或parallelStream(),stream()创建串行流,parallelStream()创建并行流
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream();
2、通过 Arrays 中的静态方法 stream() 获取数组流程
Employee[] emps = new Employee[10];
Stream<Employee> stream2 = Arrays.stream(emps);
3、通过 Stream 类的静态方法 of()
Stream<String> of = Stream.of("aa","bb","cc");
4、创建无限流
①迭代
//迭代
Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);
//遍历打印前10个数据
iterate.limit(10).forEach(System.out::println);
②生成
Stream.generate(() -> Math.random())//生成无限流:随机数
.limit(5)//取前5个值
.forEach(System.out::println);//遍历打印
多个中间操作可以连接起来形成一个流水线,除非在流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,也称为“惰性求值”。
1、筛选与切片
filter(Lambda)——接收Lambda,从流中排除某些元素
employees.stream()
.filter((e) -> e.getAge() > 30)//过滤
.forEach(System.out::println);//终止操作:遍历打印
limit(n)——截断流,使其元素不超过给定数量,该操作是一个短路操作,就是说一旦数据量达到要求后就不再迭代流中的数据,提升效率
employees.stream()
.filter((e) -> e.getAge() > 30)//过滤
.limit(2)//截断
.forEach(System.out::println);
skip(n)——跳过流中的前n个元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
employees.stream()
.filter((e) -> e.getAge() > 30)//过滤
.skip(2)//跳过
.forEach(System.out::println);
distinct——筛选,通过流所生成元素的hashCode和equals去除重复元素,因此需要流中的元素对象重写hashCode和equals方法
employees.stream()
.filter((e) -> e.getAge() > 30)//过滤
.skip(2)//跳过
.distinct()//去重,需要流中的元素对象重写hashCode和equals方法
.forEach(System.out::println);
2、映射
①map(Function f)——接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素,再将新元素放在一个新流中
List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
list.stream()
.map(str -> str.toUpperCase())
.forEach(System.out::println);
可以理解为:map会遍历流中的每一个元素,并将元素作为实参传入map中的函数中进行处理,并将返回的值放在一个新流中,最后终止操作时操作的是这个新流。
②mapToDouble(ToDoubleFunction f) ——接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream
③mapToInt(ToIntFunction f)—— 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream
④mapToLong(ToLongFunction f) ——接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream
⑤flatMap(Function f) ——接收一个函数作为参数,该函数的返回值必须是一个流(Stream),将原流中的每个值通过函数都转换成另一个流,然后把转换后的所有流连接成一个流,并实现流的扁平化,即扁平化后的流中的元素不再是一个个的流,而是一个个流中的具体的元素,与map的不同之处在于map是将函数的返回值整个作为新的元素放在一个新的流中,不会扁平化处理,类似于集合中的add(Object obj)和addAll(Collection c)
flatMap接口:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
示例:
public void test2() {
List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
list.stream()
.flatMap(TestStreamAPI::filterCharacter)//需要一个返回Stream的函数
.forEach(System.out::println);
}
/**
* 该方法必须返回一个Stream
* @param str
* @return
*/
public static Stream<Character> filterCharacter(String str) {
List<Character> list = new ArrayList<>();
for (Character ch : str.toCharArray()) {
list.add(ch);
}
return list.stream();
}
3、排序
①自然排序:sorted(),元素对象需实现Comparable接口
List<String> list = Arrays.asList("ccc", "aaa", "ddd", "bbb");
list.stream()
.sorted()//自然排序
.forEach(System.out::println);
因为String实现了Comparable接口,因此可以使用自然排序
②定制排序:sorted(Comparator com),需要传入Comparator的实现
employees.stream().sorted((e1, e2) -> {
if (e1.getAge() == e2.getAge())
return e1.getName().compareTo(e2.getName());
else
return e1.getAge().compareTo(e2.getAge());
}).forEach(System.out::println);
终止操作将不再返回流,而是对中间操作产生的流进行具体操作,可能有返回值,也可能没有。如果需要返回值但返回值有可能为空时,终止操作一般会返回一个Optional容器对象,把返回值封装在该容器对象中,可以通过调用Optional.get()获取具体的返回值,以避免出现空指针异常等。
1、查找与匹配
①allMatch(Predicate p)——检查是否匹配所有元素,流中的所有元素都满足条件
//注意返回值是boolean,而不再是一个Stream
boolean allMatch = employees.stream().allMatch((e) -> e.getStatus().equals(Status.BUSY));
②anyMatch(Predicate p)——检查是否至少匹配一个元素,只要流中有一个元素满足条件就返回true
boolean anyMatch = employees.stream().anyMatch((e) -> e.getStatus().equals(Status.BUSY));
③noneMatch(Predicate p)——检查是否没有匹配任何元素,当至少有一个元素匹配时就返回false,当所有元素都不匹配时才返回true,通过该方法得出有匹配的元素的结论
boolean noneMatch = employees.stream().noneMatch((e) -> e.getStatus().equals(Status.BUSY));
④findFirst()——返回第一个元素,调用该方法后会返回一个Optional容器对象
Optional<Employee> findFirst = employees.stream().findFirst();
Employee employee = findFirst.get();
System.out.println(employee);
⑤findAny()——返回当前流中的任意元素
Optional<Employee> findAny = employees.parallelStream()//并行流
.filter(e -> e.getStatus().equals(Status.FREE))//过滤
.findAny();
System.out.println(findAny.get());
⑥count()——返回流中元素总数
long count = employees.stream().count();
⑦max(Comparator c)——返回流中最大值
获取工资最高的员工信息:
Optional<Employee> max = employees.stream().max((e1,e2)->Double.compare(e1.getSalary(), e2.getSalary()));
⑧min(Comparator c)——返回流中最小值
获取员工的最低工资的值:
Optional<Double> min = employees.stream()
.map(Employee::getSalary)//将流转化为工资数据的流
.min(Double::compareTo);//获取工资的最小值
System.out.println(min.get());
⑨forEach(Consumer c)——内部迭代(使用 Collection 接口需要用户去做迭
代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)
2、归约与收集
①reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回值类型为 T
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(reduce);//55
说明
:reduce(0,(x,y) -> x+y)中的第一个参数0表示归约的起始值,(x,y) -> x+y是对BinaryOperator接口的实现,表示归约的规则。在这个示例中一开始会把起始值0作为x、把流中的第一个元素1作为y传入到接口函数中,再把通过规则计算出来的结果1赋值给第一个参数x,然后把流中的第二个元素2作为y再次通过规则进行计算,再将计算来的结果3赋值给第一个参数x,以此类推,直到将流中的所有元素都进行过累加操作,最后将结果返回。有些类似于递归调用。
②reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional
Optional<Double> reduce = employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(reduce.get());
map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名,通过map提取数据,通过reduce对提取的数据进行归约操作
。
③collect(Collector c) 将流转换为其他形式。接收一个 Collector 接口的实现,指明按照哪种方式搜集结果,用于给Stream中元素做汇总,功能十分强大。
List<String> collect = employees.stream()
.map(Employee::getName)//提取
.collect(Collectors.toList());//将结果搜集至list
collect.forEach(System.out::println);
Set<String> collect = employees.stream()
.map(Employee::getName)// 提取
.collect(Collectors.toCollection(HashSet::new));// 将结果搜集至HashSet
集合也有forEach方法,这是因为集合继承了Iterable:
public interface Collection<E> extends Iterable<E>
在Iterable接口中有forEach方法的实现:
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。Collectors 工具类提供了很多静态方法,可以方便地创建常见收集器的实例,具体方法与示例如下表:
方法 | 返回类型 | 用途 | 示例 |
---|---|---|---|
toList | List |
把流中元素搜集到List | List |
toSet | Set |
把流中元素收集到Set | Set |
toCollection | Collection |
把流中元素收集到创建的集合(可为任意集合类型) | Collection |
counting | Long | 计算流中元素的总数 | long count = list.stream().collect(Collectors.counting()); |
summingInt | Integer | 对流中元素的整数属性求和等 | int total = list.stream().collect(Collectors.summingInt(Employee::getSalary); |
averagingInt | Double | 计算流中元素Integer属性的平均值 | double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary)); |
summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值(如平均值、最大值、最小值、总和等) | IntSummaryStatistics iss = list.stream().collect(Collectors.summarizingInt(Employee::getSalary)); |
joining | String | 连接流中每个字符串 | String str = list.stream().map(Employee::getName).collect(Collectors.joining()); |
maxBy | Optional |
根据比较器选择最大值 | Optional |
minBy | Optional |
根据比较器选择最小值 | Optional collect = employees.stream().map(Employee::getSalary).collect(Collectors.minBy(Double::compare)); |
reducing | 归约产生的类型 | 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 | int total = list.stream().collect(Collectors.reducing(0,Employee::getSalary,Integer::sum)); |
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果转换函数 | int how = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); |
groupingBy | Map |
根据某属性值对流分组,key为K,value为List |
Map |
partitioningBy | Map |
根据true或false进行分区 | Map |
多级分组示例:先按照status分组,再按照年龄段分组
Map<Status, Map<String, List<Employee>>> collect = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((Employee e) -> {
if (e.getAge() <= 35)
return "青年";
else if (e.getAge() <= 50)
return "中年";
else
return "老年";
})));
在groupingBy中可以传入分组逻辑。
分区示例:满足条件的一个区,不满足条件的一个区,以工资是否大于5000分区
Map<Boolean, List<Employee>> collect = employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 5000));
统计示例:统计员工工资信息的平均值、员工数量、工资总和、最大和最小值
DoubleSummaryStatistics collect = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
double average = collect.getAverage();
long count = collect.getCount();
double sum = collect.getSum();
double max = collect.getMax();
double min = collect.getMin();
字符连接:joining的第一个参数是分隔符,第二个参数是连接好的字符串的前缀,第三个是连接好的字符串的后缀
String collect = employees.stream().map(Employee::getName).collect(Collectors.joining(",",">>>","<<<"));