Stream 流详细总结

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 代表数据流,流中的数据元素的数量可能是有限的,也可能是无限的。
Stream 流详细总结_第1张图片
流和集合的区别

  • 不存储数据:流是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作
  • 函数式编程:流的操作不会修改数据源,例如 filter 不会将数据源中的数据删除
  • 延迟操作:流的很多操作如 filter、map 等中间操作是延迟执行的,只有到终点操作才会将操作顺序执行
  • 可以解绑:对于无限数量的流,有些操作是可以在有限的时间完成的,比如 limit(n) 或 findFirst(),这些操作可以实现 “短路” (Short-circuiting),访问到有限的元素后就可以返回
  • 纯消费:流的元素只能访问一次,类似 lterator,操作没有回头路,如果你想从头重新访问流的元素,对不起,你得重新生成一个新的流

集合讲的是数据,流讲的是计算
Stream 流详细总结_第2张图片

二、流的创建

生成流的方式主要有五种

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);

Stream 流详细总结_第3张图片

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,并将结果收集到一个新的列表中。
运行上面代码时,会看到以下输出:
Stream 流详细总结_第4张图片
需要注意的是,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);

Stream 流详细总结_第5张图片

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

好事定律:每件事最后都会是好事,如果不是好事,说明还没到最后。

你可能感兴趣的:(#,Java基础,java)