Stream 流详细总结
- 一、Stream 是什么
- 二、流的创建
- 1、Stream 创建
- 2、Collection 集合创建(最常见的一种)
- 3、Array 数组创建
- 4、文件创建
- 5、函数创建
- 三、流的操作
- 1、中间操作
- distinct 去重
- filter 过滤
- map 映射
- flatMap 映射汇总
- sorted 排序
- limit 截断
- skip 跳过
- peek 观察
- 2、终止操作
- match 断言
- count 计数
- collect 收集
- ① toList
- ② toMap
- ③ toSet
- ④ counting
- ⑤ summingInt
- ⑥ averagingInt
- ⑦ joining
- ⑧ maxBy、minBy
- ⑨ groupingBy
- forEach 遍历
- findFirst 返回第一个元素
- max、min 最大值、最小值
- sum 求和
- concat 组合
- toXXX 转换
- reduce 规约
一、Stream 是什么
Stream 是 Java 8 新增的重要特性,它提供函数式编程支持并允许以管道方式操作集合,流操作会遍历数据源,使用管道式操作处理数据后生成结果集合,这个过程通常不会对数据源造成影响。
同时 stream 不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组、Java 容器或 I/O channel 等。在 Stream 中的操作每一次都会产生新的流,内部不会像普通集合操作一样立刻获取值,而是惰性取值,只有等到用户真正需要结果的时候才会执行。
Stream 代表数据流,流中的数据元素的数量可能是有限的,也可能是无限的。
流和集合的区别
- 不存储数据:流是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作
- 函数式编程:流的操作不会修改数据源,例如 filter 不会将数据源中的数据删除
- 延迟操作:流的很多操作如 filter、map 等中间操作是延迟执行的,只有到终点操作才会将操作顺序执行
- 可以解绑:对于无限数量的流,有些操作是可以在有限的时间完成的,比如 limit(n) 或 findFirst(),这些操作可以实现 “短路” (Short-circuiting),访问到有限的元素后就可以返回
- 纯消费:流的元素只能访问一次,类似 lterator,操作没有回头路,如果你想从头重新访问流的元素,对不起,你得重新生成一个新的流
集合讲的是数据,流讲的是计算
二、流的创建
生成流的方式主要有五种
1、Stream 创建
Stream<Integer> stream = Stream.of(1,2,3,4,5);
2、Collection 集合创建(最常见的一种)
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
integerList.add(4);
integerList.add(5);
Stream<Integer> listStream = integerList.stream();
3、Array 数组创建
int[] intArr = {1, 2, 3, 4, 5};
IntStream arrayStream = Arrays.stream(intArr);
通过 Array.stream 方法生成流,生成的是数值流 [IntStream] 而不是对象流 Stream
注:使用数值流可以避免计算过程中拆箱装箱,提高性能
Stream API 提供了 mapToInt、mapToDouble、mapToLong 三种方式将对象流 [Stream] 转换成对应的数值流,同时提供了 boxed 方法将数值流转换成对象流
4、文件创建
try {
Stream<String> fileStream = Files.lines(Paths.get("data.txt"), Charset.defaultCharset());
} catch (IOException e) {
e.printStackTrace();
}
通过 Files.lines方法得到一个流,并且得到的每个流都是给定文件中的一行
5、函数创建
iterator
Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 2).limit(5);
iterator 方法接受两个参数,第一个为初始化值,第二个为进行的函数操作,因为 iterator 生成的流为无限流,通过 limit 方法对流进行了截断,只生成 5 个偶数
generator
Stream<Double> generateStream = Stream.generate(Math::random).limit(5);
generator 方法接受一个参数,方法参数类型为 Supplier,由它为流提供值。generator 生成的流也是无限流,因此通过 limit 方法对流进行截断
三、流的操作
流的操作类型主要分为两种:中间操作、终止操作
1、中间操作
distinct 去重
distinct 保证数据源中的重复元素在结果中只出现一次,它使用 equals() 方法判断两个元素是否相等
/**filter去重方法实现类**/
private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(keyExtractor.apply(t));
}
// 1.String 集合去重
List<String> list_str = list.stream().distinct().collect(Collectors.toList());
// 2.实体对象集合去重,判断依据是整个实体对象
List<Student> list_student = list.stream(S).distinct().collect(Collectors.toList());
// 3.根据 list 中某个属性去重
List<Student> list_name1 = liststream().collect(collectingAndThen(
toCollection(() -> new
TreeSet<(Comparator.comparing(Student::getName))),
ArrayList::new));
List<Student> list_name2 = list.stream().filter(distinctByKey(Student::getName))
.collect(Collectors.toList());
在第3中情况中,我们首先创建了一个方法作为 Stream.filter() 的参数,其返回类型为 Predicate,原理就是判断一个元素能否加入到 Set 中去,用到了 Set 集合的属性
filter 过滤
filter 对所有元素进行检查,只有断言函数为真的元素才会出现在结果中,不会对数据源进行修改
// 输出不为空的字符
List<String> list_notNull = list.stream().filter(ObjectUtil::isNotNull).toList();
// 输出ID大于 6的 user对象
List<User> filetr = userList.stream().filter(x-> x.getId() > 6).collect(Collectors.toList());
map 映射
map 根据传入的mapper函数对元素一对一映射,即数据源中的每一个元素都会在结果中被替换(映射)为mapper函数的返回值,也可以根据处理返回不同数据类型
// 输出id
List<Long> list_ids = list.stream().map(ProblemList::getId).toList();
// 用指定字符连接流中的每个名称,并输出
String str_name = list.stream().map(ProblemList::getName).collect(Collectors.joining(", "));
// 输出新的IdName集合
List<IdName> list_IdName = list.stream().map(
x -> new IdName().setId(x.getId()).setName(x.getName())
).toList();
flatMap 映射汇总
flatMap 将映射后的流的元素全部放入到一个新的流中
List<IdName> list = new ArrayList<>();
list.add(new IdName().setId(1L).setName("aa,bb,cc"));
list.add(new IdName().setId(2L).setName("11,22,33"));
list.add(new IdName().setId(3L).setName("ⅠⅠ,ⅡⅡ,ⅢⅢ"));
// 数据拆分一对多映射
list.stream().flatMap(x -> Arrays.stream(x.getName().split(","))).forEach(System.out::println);
sorted 排序
sorted 将流中的元素进行排序
// 根据名字倒序
List<User> list = userList.stream().sorted(Comparator.comparing(User::getName).reversed()).toList();
limit 截断
limit 返回指定数量的前n个元素的流
// 获取前5条数据
List<User> list_limit = list.stream().limit(5).toList();
skip 跳过
skip 返回丢弃了前n个元素的流,如果流中的元素小于或者等于n,则返回空的流
// 跳过第3条取后面的数据
List<User> list_skip = list.stream().skip(3).toList();
peek 观察
peek 主要作用是在流的每个元素上执行一个操作,比如打印值、记录日志、调试等。通常用于调试和观察流的中间状态,而不会对流的内容进行修改
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> doubledNumbers = numbers.stream()
.peek(n -> System.out.println("Processing number: " + n))
.map(n -> n * 2)
.collect(Collectors.toList());
创建一个整数列表 numbers,然后通过流的方式对每个元素进行处理。在流的 peek 操作中,打印元素的值,然后我们使用 map 操作将每个数字乘以2,并将结果收集到一个新的列表中。
运行上面代码时,会看到以下输出:
需要注意的是,peek 方法是一个中间操作,它不会触发流的终端操作。如果你希望对流的内容进行修改或获取最终的结果,你需要在 peek 方法之后添加一个终止操作,比如 collect、forEach 等。
//每个用户ID加1输出
userList.stream().peek(user -> user.setId(user.getId()+1)).forEach(System.out::println);
2、终止操作
match 断言
- allMatch :只有在所有元素都满足断言时才返回 true,否则返回 false,流为空时总是返回 true
- anyMatch :只有在任意一个元素满足断言时返回 true,否则返回 false
- noneMatch :只有在所有元素都不满足断言时才返回 true,否则返回 false
// allMatch:检查是否匹配所有元素
boolean matchAll = userList.stream().allMatch(user -> "北京".equals(user.getCity()));
// anyMatch:检查是否至少匹配一个元素
boolean matchAny = userList.stream().anyMatch(user -> "北京".equals(user.getCity()));
// noneMatch:检查是否没有匹配所有元素,返回boolean
boolean nonaMatch = userList.stream().allMatch(user -> "云南".equals(user.getCity()));
count 计数
count 返回流中的元素的数量
long count = userList.stream().filter(user -> user.getAge() > 20).count();
collect 收集
collect 将流转换为其他形式。接收一个 Collector 接口的实现,用于给 stream 中元素做汇总的方法。下面是一些常见的搜集器:
① toList
把流中元素收集到 List 集合中
// 将用户ID存放到List集合中
List<Integer> idList = userList.stream().map(User::getId).collect(Collectors.toList());
② toMap
把流中元素收集到 Map 集合中
// 将 ID和 user以 Key-Value形式存放到 Map集合中
Map<Long,User> userMap = list.stream().collect(Collectors.toMap(User::getId, x -> x, (a,b)->a));
注意,这里的 toMap方法中多了一个参数【(a,b) -> a】,表示如果遇到 Key相同的情况,Value保存新值(b)或旧值(a)
③ toSet
把流中元素收集到 Set 集合中
// 将用户所在城市存放到Set集合中
Set<String> citySet = userList.stream().map(User::getCity).collect(Collectors.toSet());
④ counting
计算流中元素的个数
// 符合条件的用户总数
long count = userList.stream().filter(user -> user.getId()>1).collect(Collectors.counting());
⑤ summingInt
对流中元素的整数属性求和
// 计算ID大于2的用户ID之和
Integer sumInt = userList.stream()
.filter(user -> user.getId()>2)
.collect(Collectors.summingInt(User::getId));
⑥ averagingInt
计算元素 Integer 属性的均值
// 计算用户的平均年龄
Double avg = userList.stream().collect(Collectors.averagingInt(Student::getAge));
⑦ joining
连接流中的每个字符串
// 将用户所在城市,以指定分隔符链接成字符串
String joinCity = userList.stream().map(User::getCity).collect(Collectors.joining("||"));
⑧ maxBy、minBy
根据比较器选择最大值、最小值
// 筛选元素中年龄最大的用户
User maxId = userList.stream().collect(Collectors.maxBy(Comparator.comparingLong(User::getAge))).get();
// 筛选元素中ID最小的用户
User maxId = userList.stream().collect(Collectors.minBy(Comparator.comparingLong(User::getId))).get();
⑨ groupingBy
根据某属性值对流分组,属性为K,结果为V
// 以城市对用户进行分组
Map<String,List<User>> groupCity = userList.stream().collect(Collectors.groupingBy(User::getCity));
forEach 遍历
forEach 遍历流的每一个元素,执行指定的 action。它是一个终点操作,但不保证按照流的 encounter order 顺序执行,对于有序流要按照顺序执行可使用 forEachOrdered 方法
list.stream().forEach(System.out::println);
findFirst 返回第一个元素
findFirst 返回第一个元素,如果流为空,返回空的Optional
User user = list.stream().findFirst().orElse(null);
orElse( null ):表示如果一个都没找到 返回 null;orElse() 中可以塞默认值,如果找不到就会返回默认值
max、min 最大值、最小值
max、min 返回流中的最大值、最小值
Long max_id = list.stream().map(User::getId).max(Long::compare).orElse(0L);
Integer min_age = list.stream().map(User::getAge).min(Comparator.comparing(x -> x)).orElse(null);
sum 求和
sum 求流的某属性之和
// 求ID之和
int sum = userList.stream().mapToInt(User::getId).sum();
concat 组合
concat 用来连接类型一样的两个流
// 求ID之和
List<Integer> list1 = Arrays.asList(1,2,3);
List<Integer> list2 = Arrays.asList(4,3,2);
Stream.concat(list1.stream(),list2.stream()).forEach(System.out::println);
toXXX 转换
toXXX toArray 将一个流转换成数组,如果想转换成其他集合类型,也可调用 collect 方法,利用 Collectors.toXXX方法进行转换
List<User> toList = userList.stream().toList();
Integer[] integers = Stream.of(1, 2, 3, 4, 5).toArray(Integer[]::new);
reduce 规约
reduce 把stream中的元素组合起来
它需要我们首先提供一个起始种子,然后依照某种运算规则使其与stream的第一个元素发生关系产生一个新的种子,这个新的种子再紧接着与stream的第二个元素发生关系产生又一个新的种子,就这样依次递归执行,最后产生的结果就是reduce的最终产出
// 没有起始值,返回为 Optional类型
Optional<Long> reduce = list.stream().map(ProblemList::getId).reduce(Long::sum); // 结果:15
// 给一个起始值 1
Long reduce_1 = list.stream().map(ProblemList::getId).reduce(1L, Long::sum); // 结果:16
好事定律:每件事最后都会是好事,如果不是好事,说明还没到最后。