jdk1.8 List集合Stream流式处理

jdk1.8 List集合Stream流式处理

  • 一、介绍(为什么需要流Stream,能解决什么问题?)
    • 1.1 什么是 Stream?
    • 1.2 常见的创建Stream方法
    • 1.3 常见的中间操作
    • 1.4 常见的终端操作
  • 二、创建流Stream
    • 2.1 Collection的.stream()方法
    • 2.2 数组创建流
    • 2.3 静态工厂方法
    • 2.4 Stream.builder
    • 2.5 从文件创建流
  • 三、中间操作
    • 3.1 过滤(Filter)
    • 3.2 映射(Map)
    • 3.3 映射(flatMap)
    • 3.4 排序(Sorted)
    • 3.5 distinct(去重,含转Set去重)
    • 3.6 skip limit(分页)
    • 3.7 peek(循环)
    • 3.8 mapToInt、mapToDouble ...
  • 四、终端操作
    • 4.0 foreach(常用于处理List、Map等数据)
    • 4.1 收集(Collect)
      • 4.1.1 转换为List
      • 4.1.2 转换为Set
      • 4.1.3 分组汇总(Grouping)
      • 4.1.4 toMap(转成Map)
      • 4.1.5 reducing 规约(此处只讨论简单使用,重载方法待研究)
      • 4.1.6 计数(Counting)
      • 4.1.7 汇总求和(Summing)
      • 4.1.8 汇总对象(IntSummaryStatistics)
      • 4.1.9 获取单个元素 maxBy、 minBy
      • 4.1.10 toCollection()
      • 4.1.11 mapping
      • 4.1.12 joining
      • 4.1.13 partitioningBy
      • 4.1.14 collectingAndThen
    • 4.2. 自定义收集器
    • 4.3 归约(Reduce)
    • 4.4 查找元素 findFirst、findAny
    • 4.5 匹配 anyMatch、allMatch、noneMatch
    • 4.6 count max min
  • 五、并行流
  • 六、总结
    • 6.1 Stream的优点:
    • 6.2 Stream的缺点

一、介绍(为什么需要流Stream,能解决什么问题?)

Java 8 引入了一个新的抽象层——Stream API,它允许你以声明性方式处理数据集合(包括数组、集合等)。Stream API 提供了一种高效且易于表达的方式来处理数据集合,包括过滤、排序、映射和归约等操作。这种处理方式极大地提高了代码的可读性和可维护性,同时也提升了处理大量数据的性能。

1.1 什么是 Stream?

Stream(流)是 Java 8 引入的一个关键抽象概念,它代表了一个来自数据源的元素队列并支持聚合操作。和迭代器(Iterator)不同,Stream 不存储元素;它们是源到聚合操作的中间桥梁,其操作的执行是延迟的,即只有在需要结果时才执行。
在日常编程中,会经常进行数据(如List、数组)的处理,在没有stram流时,我们的一般操作方式显得比较臃肿,不够优雅简洁。代码如下(原写法):

        List<String> list = new ArrayList<>();Collections.addAll(list,"赵子龙","关云长","黄忠","张良","张翼德");ArrayList<String> list1 = new ArrayList<>();list.forEach(s -> {if(s.startsWith("张")) {list1.add(s);}});list1.forEach(s -> System.out.println(s));

stream流式写法

        List<String> list = new ArrayList<>();Collections.addAll(list,"赵子龙","关云长","黄忠","张良","张翼德");list.stream().filter(p -> p.startsWith("张")).forEach(System.out::println);

这个例子也告诉我们Stream流写法更加简洁优雅。
流式操作三部曲如下图所示:
在这里插入图片描述
如上图所示,概括的讲,可以将stream流操作分为3种类型

  • 创建Stream
  • Stream中间操作
  • 终止Stream(终端操作)

每个Stream管道操作类型都包含若干API方法,先列举下各个API方法的功能介绍。

1.2 常见的创建Stream方法

在Java中,创建Stream的方法多种多样,可以从各种数据源生成Stream。以下是一些常见的创建Stream的方法:

方法类型示例代码描述
集合转StreamList<String> list = Arrays.asList("a", "b", "c");<br>Stream<String> stream = list.stream();通过Collection接口(如List、Set等)的stream()方法创建Stream。
数组转StreamString[] array = {"d", "e", "f"};<br>Stream<String> stream = Arrays.stream(array);通过Arrays类的stream(T[] array)静态方法,将数组转换为Stream。
Stream类静态方法Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);使用Stream类的of(T... values)静态方法,从一组值中创建Stream。
无限流生成Stream<Double> randomStream = Stream.generate(Math::random);
Stream<Integer> infiniteStream = Stream.iterate(0, i -> i + 1);
使用Stream类的generate(Supplier<T> s)iterate(T seed, UnaryOperator<T> f)静态方法生成无限流。generate接受一个供应器(Supplier),每次需要新值时调用其get()方法;iterate接受一个初始值和一个函数,每次迭代时将当前值作为参数传递给函数,返回的结果作为下一次迭代的值。
空StreamStream<String> emptyStream = Stream.empty();使用Stream类的empty()静态方法创建一个空的Stream。
构建器Stream.Builder<String> builder = Stream.builder();<br>builder.add("Java");<br>builder.add("Python");<br>Stream<String> stream = builder.build();使用Stream.Builder来构建复杂的Stream。首先创建一个Builder对象,然后调用其add方法添加元素,最后调用build方法生成Stream。

1.3 常见的中间操作

操作名称操作方法描述
过滤filter(Predicate<? super T> predicate)通过给定的谓词(predicate)测试元素,保留使谓词返回true的元素
映射map(Function<? super T, ? extends R> mapper)将每个元素映射到其对应的转换结果上,转换结果可以是一个新的类型
扁平映射flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)类似于map,但每个元素都可以被映射成一个Stream,然后所有这些Stream会被合并成一个Stream
排序sorted() / sorted(Comparator<? super T> comparator)对流中的元素进行排序,可以选择自然排序或自定义排序器
截断limit(long maxSize)限制流中元素的数量,使其不超过给定的最大值
跳过skip(long n)跳过流中的前n个元素
并行流.parallel()将顺序流转换为并行流,以便并行处理
串行流.sequential()将并行流转换回顺序流,以便顺序处理
去重distinct()返回一个由流中不同元素组成的流(基于元素的equalshashCode方法)
逐元素替换peek(Consumer<? super T> action)提供一个消费者函数,该函数会在处理每个元素时执行,但不影响流的内容

请注意,尽管peek操作可以看作是一种中间操作(因为它返回Stream本身),但它主要用于调试目的,并不改变流的内容或结构。

1.4 常见的终端操作

操作名称操作方法描述
匹配操作anyMatch(Predicate<? super T> predicate)是否存在至少一个元素匹配给定的谓词?
allMatch(Predicate<? super T> predicate)是否所有元素都匹配给定的谓词?
noneMatch(Predicate<? super T> predicate)是否没有元素匹配给定的谓词?
查找操作findFirst()返回流中的第一个元素(作为Optional),如果流为空,则返回Optional.empty()
findAny()返回流中的任意元素(作为Optional),对于并行流,行为可能不同
归约操作reduce(BinaryOperator<T> accumulator)通过给定的归约操作(如求和、求积)将流中的所有元素组合起来,结果为Optional
reduce(T identity, BinaryOperator<T> accumulator)类似于上一个,但提供了一个初始值,结果为T类型,而非Optional
收集操作collect(Collectors.toList()) / collect(Collectors.toSet())将流中的元素收集到一个列表、集合或其他容器中,Collectors类提供了多种收集器
最大值/最小值max(Comparator<? super T> comparator) / min(Comparator<? super T> comparator)根据给定的比较器找到流中的最大或最小元素,结果为Optional
数组操作toArray(T[] generator) / toArray(IntFunction<T[]> generator)将流中的元素收集到一个数组中,需要提供一个数组生成器或类型信息
计数操作count()返回流中的元素数量,结果为long类型
遍历操作forEach(Consumer<? super T> action)对流中的每个元素执行给定的操作,这是一个终端操作,因为它有副作用(即执行操作)
归纳操作summaryStatistics()(针对IntStream, LongStream, DoubleStream)返回包含各种统计数据的对象,如计数、平均值、最大值、最小值等

请注意,forEach虽然是一个终端操作,但它主要用于执行副作用(即不返回结果的操作),而不是为了获取结果。同样,reducecollectmax/min等操作既可以看作是中间操作(因为它们可以在另一个流的上下文中使用,尽管这在实际中并不常见),也可以看作是终端操作(因为它们会触发流的执行并返回一个结果)。但是,在典型的用法中,我们更倾向于将它们视为终端操作。

二、创建流Stream

2.1 Collection的.stream()方法

    public static void main(String[] args) {// 1.list创建流List<String> list = new ArrayList<>();Collections.addAll(list, "赵子龙", "关云长", "黄忠", "张良", "张翼德");// 创建一个Stream流Stream<String> stream = list.stream();// 使用流进行操作stream.filter(p -> p.startsWith("张")).forEach(System.out::println);// 2.map创建流(先转换成keySet或entrySet)Map<Object, Object> map = new HashMap<>();map.put("张三", 1);map.put("李四", 8);map.put("王五", 5);// 2.1 keySetmap.keySet().stream().forEach(System.out::println);map.keySet().stream().forEach(p -> System.out.println(p + "= " + map.get(p)));// 2.2 entrySetmap.entrySet().stream().forEach(System.out::println);}

示例中,用list.stream()创建流,用map装换成keySet或entrySet后用.stream()创建流。

2.2 数组创建流

        int[] array = {5,6,8,7,5,6,1,0};// 1.从整数数组创建IntStreamIntStream intStream = Arrays.stream(array);String[] strArray = {"东邪","西毒","南帝","北丐","中神通"};// 2.从字符串数组创建StreamStream<String> stringStream = Arrays.stream(strArray);

示例中,用Arrays.stream()创建流(IntStream 、Stream),为开发中最常用创建流的方式。

2.3 静态工厂方法

Stream.of(T… values):通过将一个可变参数的元素列表传递给Stream.of方法来创建一个包含这些元素的Stream流。这对于创建具有少量元素的流非常方便。
Stream.empty():使用Stream.empty()方法创建一个空的Stream流。
Stream.generate(Supplier s):通过提供一个Supplier函数来创建一个无限大小的Stream流,该函数会生成元素。通常,需要使用limit操作限制生成的元素数量。
Stream.iterate(T seed, UnaryOperator f):通过提供初始值(seed)和一个一元操作函数(UnaryOperator)来创建一个包含无限序列的Stream流。例如,可以使用Stream.iterate(0, n -> n + 1)来创建一个自然数序列的Stream流。

  • Stream.of(T… values)创建流
		// 1.String数组String[] strArr = {"东邪","西毒","南帝","北丐","中神通"};Stream<String> strArrStream = Stream.of(strArr);strArrStream.forEach(System.out::println);// 2.注意:int类型数组,会将数组仅当作一个处理,如下图所示打印的是一个int[]的地址int[] intArr = {5,6,8,7,5,6,1,0};Stream<int[]> intArrStram = Stream.of(intArr);intArrStram.forEach(System.out::println);

打印如下:
在这里插入图片描述

  • Stream.generate(Supplier s)创建流
        // generate创建streamStream<Integer> randomIntStream = Stream.generate(() -> new Random().nextInt(100));randomIntStream.limit(10).forEach(System.out::println);

2.4 Stream.builder

  • Stream.builder创建Stream<Integer>
        // 1.逐个添加1到10的整数Stream.Builder<Integer> builder = Stream.builder();for (int i = 0; i < 10; i++) {builder.accept(i);}Stream<Integer> builderIntStream = builder.build();
  • Stream.builder创建Stream<Long>
        // 2.斐波那契数列的前10个数字Stream.Builder<Long> builder1 = Stream.builder();long a = 0; long b = 1;int count = 10;for (int i = 0; i < count; i++) {builder1.accept(a);long next = a + b;a = b;b = next;}Stream<Long> longStream = builder1.build();

2.5 从文件创建流

  • 使用Files.lines方法创建文本文件的流
        // 逐行读取,创建StreamString path = "names.txt";try {Stream<String> lines = Files.lines(Paths.get(path));List<String> names = lines.collect(Collectors.toList());} catch (IOException e) {throw new RuntimeException(e);}

三、中间操作

基础代码创建list

        List<User> userList = new ArrayList<>();User user1 = User.builder().name("曹操").age(12).desc("许昌").build();User user2 = User.builder().name("刘备").age(22).desc("西蜀").build();User user3 = User.builder().name("曹操").age(42).desc("许昌").build();User user4 = User.builder().name(null).age(32).desc("许昌").build();userList.add(user1);userList.add(user2);userList.add(user3);userList.add(user4);

3.1 过滤(Filter)

filter(Predicate<? super T> predicate) 方法接收一个谓词(即返回布尔值的函数),并返回一个包含所有匹配该谓词的元素的流。如下所示,常规过滤可直接简单写,复杂的过滤,可抽取方法,使代码更加清晰。

{...// 1.常规简单过滤List<User> overNList1 = userList.stream().filter(p -> p.getAge() > 18).collect(Collectors.toList());// 2.复杂过滤时,可抽取方法List<User> overNList2 = userList.stream().filter(p -> isOverN(p)).collect(Collectors.toList());...
}// 示例:isOverNprivate static boolean isOverN(User p) {return p.getAge() > 18;}

3.2 映射(Map)

map(Function<? super T, ? extends R> mapper) 方法将流中的每个元素映射到另一个对象,并返回一个新的流。

        // 1.装换成简单数据集合,如StringList<String> names = userList.stream().map(User::getName).collect(Collectors.toList());// 2.装换成其它对象集合List<Address> addressList = userList.stream().map(p -> Address.builder().content(p.getDesc()).build()).collect(Collectors.toList());// 3.mapToInt maxToDouble maxToLong等,此类转换好处是可以获取如max、min、average、count等统计类数据long count = userList.stream().mapToInt(User::getAge).count();System.out.println("count = " + count);

3.3 映射(flatMap)

flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) flatMap操作与map类似,但它被设计用来处理那些将每个输入元素映射到多个输出元素的场景。flatMap首先应用一个函数来映射每个元素,这个函数返回一个流(而不是单个元素)。然后,flatMap将这些流“扁平化”成一个流。

List<String> sentences = Arrays.asList("apple,banana", "cherry,date");  
List<String> words = sentences.stream()  .flatMap(sentence -> Arrays.stream(sentence.split(",")))  .collect(Collectors.toList());  
// words: ["apple", "banana", "cherry", "date"]

适用于元素逐个拆分,并组成新的集合。每个元素处理并返回一个新的Stream,然后将多个Stream展开合并为了一个完整的新的Stream。

3.4 排序(Sorted)

sorted()sorted(Comparator<? super T> comparator) 方法对流中的元素进行排序,关于sorted的用法非常多。

        // 0.默认规则排序 适用于基本类型List<String> users = Arrays.stream(new String[]{"东邪", "西毒", "南帝", "北丐", "中神通"}).sorted().collect(Collectors.toList());// 1.自定义排序规则List<User> users2 = userList.stream().sorted((o1, o2) -> o2.getAge().compareTo(o1.getAge())).collect(Collectors.toList());// 2.年龄排序 倒叙List<User> ageSortedList = userList.stream().sorted(Comparator.comparing(User::getAge).reversed()).collect(Collectors.toList());// 3.姓名排序 (忽略大小写)List<User> nameSortedList = userList.stream().sorted(Comparator.comparing(User::getName, String.CASE_INSENSITIVE_ORDER)).collect(Collectors.toList());// 4.先按年龄排序,如果年龄相同则按姓名排序List<User> ageAndNameSortedList = userList.stream().sorted(Comparator.comparing(User::getAge).thenComparing(User::getName, String.CASE_INSENSITIVE_ORDER)).collect(Collectors.toList());// 5.排序处理null值 nullsLast或nullsFirst// 6.null排在最前面,其他非null对象根据age倒叙排列 Comparator.reverseOrder()List<User> nullLastSortedList = userList.stream().sorted(Comparator.comparing(User::getAge, Comparator.nullsFirst(Comparator.reverseOrder()))).collect(Collectors.toList());nullLastSortedList.forEach(System.out::println);// 7.null排在最前面,其他非null对象根据age正序排列 Comparator.naturalOrder()List<User> nullLastSortedList1 = userList.stream().sorted(Comparator.comparing(User::getAge, Comparator.nullsFirst(Comparator.naturalOrder()))).collect(Collectors.toList());nullLastSortedList1.forEach(System.out::println);// 8.null排在最前面,其他非null对象根据age正序排列,并将最终结果反转List<User> nullLastSortedList2 = userList.stream().sorted(Comparator.comparing(User::getAge, Comparator.nullsFirst(Comparator.naturalOrder())).reversed()).collect(Collectors.toList());nullLastSortedList2.forEach(System.out::println);System.out.println("++++++++++++++++++++++++++++++++");// 9.先根据age排序,null排在最前面,其他的倒序排列; null相同的按照level正序排列  level中为null的排前面; 其他的正序排列List<User> nullLastSortedList3 = userList.stream().sorted(Comparator.comparing(User::getAge, Comparator.nullsFirst(Comparator.reverseOrder())).thenComparing(Comparator.comparing(User::getLevel, Comparator.nullsFirst(Comparator.naturalOrder())).reversed())).collect(Collectors.toList());nullLastSortedList3.forEach(System.out::println);System.out.println("///");// 10.先根据age排序,null排在最前面,其他的正序排列;List<User> nullLastSortedList4 = userList.stream().sorted(Comparator.comparing(User::getAge, Comparator.nullsFirst(Integer::compareTo))).collect(Collectors.toList());nullLastSortedList4.forEach(System.out::println);// 11.先根据age排序,null排在最前面,其他的正序排列,并将最终结果反转List<User> nullLastSortedList6 = userList.stream().sorted(Comparator.comparing(User::getAge, Comparator.nullsFirst(Integer::compareTo).reversed())).collect(Collectors.toList());nullLastSortedList6.forEach(System.out::println);// 12.自定义复杂排序规则userList.stream().sorted(new Comparator<User>() {@Overridepublic int compare(User o1, User o2) {Date o1OpTime = DateUtil.parse(o1.getOpTime(), DateUtil.FULL_DAY_FORMAT);Date o2OpTime = DateUtil.parse(o2.getOpTime(), DateUtil.FULL_DAY_FORMAT);// 正序
//                return o1OpTime.compareTo(o2OpTime);// 倒叙return o2OpTime.compareTo(o1OpTime);}});

其中,nullsLast同理。

3.5 distinct(去重,含转Set去重)

distinct() 为基础数据简单去重操作, 后文会讲到通过 Collectors.toCollection结合TreeSet排序去重

        // 1.基本类型简单去重List<String> list = Arrays.asList(new String[]{"刘备", "张飞", "赵云", "刘备"});List<String> collect = list.stream().distinct().collect(Collectors.toList());collect.forEach(System.out::println);// 终端操作去重// 2.toCollection()去重:根据对象某个字段去重(去重规则:取第一条数据,可排序后再去重,如下所示:取年龄最小的对象)TreeSet<User> nameSet = userList.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))));// 3.toCollection()去重:自定义规则去重(同样可先排序再去重)TreeSet<User> nameSet2 = userList.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toCollection(() -> new TreeSet<User>(new Comparator<User>() {@Overridepublic int compare(User o1, User o2) {return (o1.getName() + o1.getLevel()).equals(o2.getName() + o2.getLevel()) ? 0 : o1.getName().equals(null) ? 1 : -1 ;}})));nameSet.stream().forEach(System.out::println);

3.6 skip limit(分页)

skip(long n)limit(long maxSize)搭配实现分页

        int pageSize = 3;int pageNo = 2;// 实现分页功能List<User> users = userList.stream().skip(pageSize * (pageNo - 1)).limit(pageSize).collect(Collectors.toList());users.stream().forEach(System.out::println);

3.7 peek(循环)

peek(Consumer<? super T> action)实现中间操作的循环操作

        // peek为中间操作,不对元素进行操作,可用于记录日志、或调试,与foreach的区别是:peek为中间操作(须有终端操作才会执行Consumer)List<User> users3 = userList.stream().peek(p -> Syste
## 3.8 takeWhilem.out.println(p.getName())).collect(Collectors.toList());

3.8 mapToInt、mapToDouble …

		// mapToInt maxToDouble maxToLong等,此类转换好处是可以获取如max、min、average、count等统计类数据long count = userList.stream().mapToInt(User::getAge).count();System.out.println("count = " + count);

四、终端操作

4.0 foreach(常用于处理List、Map等数据)

		// 循环逐个元素userList.stream().forEach(System.out::println);

4.1 收集(Collect)

collect操作接受一个Collector实例,该实例封装了归约操作的性质,比如如何添加元素、如何合并两个归约结果以及如何完成归约过程等。不过,在实际使用中,我们很少直接创建Collector实例,而是使用Collectors工具类提供的静态方法,这些方法返回了预定义的Collector实例。

以下是一些使用collect操作的常见示例:

4.1.1 转换为List

将流中的元素收集到一个列表中,可以使用Collectors.toList()

        // 1.不修改元素类型,可做完若干中间操作后,汇聚成listList<User> users = userList.stream().collect(Collectors.toList());// 2.转换成其他类型List<String> names = userList.stream().map(User::getName).collect(Collectors.toList());

4.1.2 转换为Set

如果你想要去除流中的重复元素并将结果收集到一个集合中,可以使用Collectors.toSet()

 		// 1.转换成Set, 仅基本类型可使用Set<String> set = Arrays.asList(new String[]{"刘备", "张飞", "赵云", "刘备"}).stream().collect(Collectors.toSet());set.stream().forEach(System.out::println);

4.1.3 分组汇总(Grouping)

使用Collectors.groupingBy可以将流中的元素根据某些属性进行分组。

		// 分组 单个字段可简写,多字段组合如下所示Map<String, List<User>> groupList1 = userList.stream().collect(Collectors.groupingBy(User::getName));Map<String, List<User>> groupList2 = userList.stream().collect(Collectors.groupingBy(p -> p.getName() + p.getLevel()));

4.1.4 toMap(转成Map)

默认规定:允许key为null,不允许value为null, 推荐使用3.1写法

        // 1.key不重复且value不为null的情况下,可用如下简单写法(为了保险起见,不推荐这种写法)Map<String, User> collect1 = userList.stream().collect(Collectors.toMap(User::getName, p -> p));// 2.处理key重复问题,重复时,取最新的valueMap<String, Integer> collect2= userList.stream().collect(Collectors.toMap(User::getName, User::getLevel, (oldValue, newValue) -> newValue));// 3.处理value为null问题// 3.1 方案一:Optional设置orElse值,如0,空字符串等 (***推荐***),这种写法避免了key重复,也可以避免value为null的情况Map<String, Integer> map3 = userList.stream().collect(Collectors.toMap(User::getName, p -> Optional.ofNullable(p.getLevel()).orElse(0), (oldV, newV) -> newV));// 3.2 方案二:规约转换,null值取最新的Map<String, Integer> collect = userList.stream().collect(HashMap::new, (resultMap, item) -> resultMap.put(item.getName(), item.getLevel()), HashMap::putAll);collect.entrySet().forEach(System.out::println);

4.1.5 reducing 规约(此处只讨论简单使用,重载方法待研究)

     	// 1.不指定初始值返回OptionalOptional<Integer> reduceSum = userList.stream().map(User::getAge).collect(Collectors.reducing(Integer::sum));// 2.指定初始值,返回intInteger reduceDefaultSum = userList.stream().map(User::getAge).collect(Collectors.reducing(0, Integer::sum));// 3.也可规约汇总对象User reducingLevel = userList.stream().collect(Collectors.reducing(new User(), (o1, o2) -> User.builder().age(o1.getAge() + o2.getAge()).level(o1.getLevel() + o2.getLevel()).build()));

4.1.6 计数(Counting)

Collectors.counting()可以返回一个收集器,它计算流中的元素数量。

        // 计数,与list.size()同效int size = userList.size();Long count = userList.stream().collect(Collectors.counting());

4.1.7 汇总求和(Summing)

        // 获取单个字段的和summingIntInteger ageSum = userList.stream().collect(Collectors.summingInt(User::getAge));

4.1.8 汇总对象(IntSummaryStatistics)

如果你有一个包含数值的流,并想要计算这些数值的总和,平均值,最大值,最小值等,可以使用Collectors.summingInt()(对于整数)等方法。

        // 统计对象 IntSummaryStatistics,可根据此对象获取max、min...IntSummaryStatistics intSummaryStatistics = userList.stream().collect(Collectors.summarizingInt(User::getAge));long sum1 = intSummaryStatistics.getSum();long count1 = intSummaryStatistics.getCount();int max = intSummaryStatistics.getMax();int min = intSummaryStatistics.getMin();double average = intSummaryStatistics.getAverage();

4.1.9 获取单个元素 maxBy、 minBy

        // 根据某个字段取最小值(最大值等)对应的元素 maxBy minBy,返回Optional对象Optional<User> minUserOptinal = userList.stream().collect(Collectors.minBy(Comparator.comparing(User::getAge)));User user = minUserOptinal.orElse(null);User maxUser = userList.stream().collect(Collectors.maxBy(Comparator.comparing(User::getAge))).orElse(new User());

4.1.10 toCollection()

.toCollection() 是一个收集器(Collector),它用于将流中的元素收集到一个指定的Collection类型中。这个收集器接受一个Supplier作为参数,其中C是Collection的一个子类型,这个Supplier用于提供一个新的、空的Collection实例,流中的元素将被添加到这个实例中。

        // toCollection 常与TreeSet去重使用// 1.简单转SetTreeSet<User> userTreeSet = userList.stream().collect(Collectors.toCollection(TreeSet::new));// 2.基本类型转SetTreeSet<String> nameTreeSet = userList.stream().map(User::getName).collect(Collectors.toCollection(TreeSet::new));// 3.自定义不可重复(去重)规则TreeSet<User> userDefineSet = userList.stream().collect(Collectors.toCollection(() -> new TreeSet<>(new Comparator<User>() {@Overridepublic int compare(User o1, User o2) {return o1.getName().compareTo(o2.getName());}})));

4.1.11 mapping

  • 简单使用mapping
        // 1.与.map等效List<String> names1 = userList.stream().map(User::getName).collect(Collectors.toList());List<String> names2 = userList.stream().collect(Collectors.mapping(User::getName, Collectors.toList()));
  • 分组后转换list元素的类型,复杂对象的属性转换和收集
// 		2.与groupBy搭配使用,分组后,变换元素类型 User -> AddressMap<String, List<Address>> collect = userList.stream().collect(Collectors.groupingBy(User::getName, Collectors.mapping(p -> {Address address = new Address();address.setContent(p.getDesc());return address;}, Collectors.toList())));// 与groupBy和join结合使用 拼接字符串Map<String, String> map1 =userList.stream().collect(Collectors.groupingBy(User::getName, Collectors.mapping(User::getDesc, Collectors.joining(",", "[", "]"))));map1.entrySet().stream().forEach(p -> {System.out.println(p.getKey() + ":" + p.getValue());});

4.1.12 joining

        // 常用于字符串拼接String str = list.stream().collect(Collectors.joining(",", "[", "]"));System.out.println("str = " + str);

4.1.13 partitioningBy

        // partitioningBy 根据Predicate<? super T> predicate,汇聚成含有两个元素的map,//  其中一个key为Boolean.TRUE,另一个为Boolean.FALSE,value为满足条件的集合Map<Boolean, List<User>> collect1 = userList.stream().collect(Collectors.partitioningBy(p -> {return p.getAge() >= 18;}, Collectors.toList()));

4.1.14 collectingAndThen

该方法的作用可以简单地概括为“先收集,再做一些操作”。

                // 1.常用于去重后TreeSet转成List// 解析 通过Collectors.toCollection收集TreeSet排序去重,然后通过collectingAndThen收集转成ListList<User> userList5 = userList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new));// 最终结果可以理解为如下操作TreeSet<User> treeSet = userList.stream().collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))));List<User> users = new ArrayList<>(treeSet);// 2.收集成TreeSet<User>后,转成List<Address>, 详细写法List<Address> addressList = userList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), p -> {List<Address> list1 = new ArrayList<>();p.stream().forEach(c -> {Address build = Address.builder().content(c.getDesc()).build();list1.add(build);});return list1;}));//3.简化流式写法如下userList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), p -> p.stream().collect(Collectors.mapping(c -> Address.builder().content(c.getDesc()).build(), Collectors.toList()))));

4.2. 自定义收集器

你还可以使用Collectors.collectingAndThen()Collector.of()来创建自定义的收集器。

List<String> namesList = names.stream().collect(Collectors.collectingAndThen(Collectors.toList(),Collections::unmodifiableList));

上面的代码示例将流中的元素收集到一个列表中,并立即将这个列表转换为不可修改的列表。

4.3 归约(Reduce)

reduce(BinaryOperator<T> accumulator) 方法通过重复结合流中的元素,将它们归约成一个值。

        // 1.无初始值 注意orElse为Optional的方法(不推荐)Integer sumOrElse = userList.stream().map(User::getAge).reduce(Integer::sum).orElse(0);// 2.有初始值这种形式的 reduce 接受一个初始值和一个 BiFunction<T, T, T> (推荐)Integer sum = userList.stream().map(User::getAge).reduce(0, Integer::sum);// 3.对象合并,可累加、累乘等User reduce = userList.stream().reduce(new User(), (o1, o2) -> {return User.builder().age(o1.getAge() + o1.getAge()).level(o1.getLevel() & o2.getLevel()).money(o1.getMoney() + o2.getMoney()).build();});

推荐使用有初始值的 reduce,因为它可以优雅地处理空流的情况,并允许你指定一个明确的初始值(可不为0)。

reduce 操作非常灵活,不仅可以用于求和、求积等简单单数据的聚合操作,还可以实现更复杂的归约逻辑,如字符串连接、对象合并(如案例3)等。

4.4 查找元素 findFirst、findAny

        // 1.可结合排序使用,获取第一个元素Optional<User> first = userList.stream().findFirst();// 2.任意元素Optional<User> any = userList.stream().findAny();

4.5 匹配 anyMatch、allMatch、noneMatch

        // 1.anyMatch有一个满足条件就返回trueboolean anyMatch= userList.stream().anyMatch(p -> p.getAge() == p.getLevel());// 2.allMatch所有元素都满足条件就返回trueboolean allMatch = userList.stream().allMatch(p -> p.getAge() == p.getLevel());// 3.noneMatch 没有元素满足条件就返回trueboolean noneMatch = userList.stream().noneMatch(p -> p.getAge() == p.getLevel());

4.6 count max min

        // 1.获取流中元素的个数,如果List,则与list.size()等效long count = userList.stream().count();// 2.根据age字段获取age最大值对应的对象,返回Optional,可用orElse规避nullUser user = userList.stream().max(Comparator.comparing(User::getAge)).orElse(new User());// 3.同理,根据level字段获取level最小值对应的对象User user6 = userList.stream().min(Comparator.comparing(User::getLevel)).orElse(new User());

五、并行流

Java 8 引入了并行流的概念,允许你以并行方式处理数据集合,以利用多核处理器的优势。你可以通过调用 stream() 方法的并行版本 parallelStream() 来获取一个并行流。

List<String> strings = Arrays.asList("apple", "banana", "cherry", ...);
long count = strings.parallelStream().filter(s -> s.startsWith("a")).count();

注意:并行流可能会同时处理多个元素,因此流中的操作必须是线程安全的。

六、总结

6.1 Stream的优点:

  • 代码更简洁、偏声明式的编码风格,更容易体现出代码的逻辑意图
  • 逻辑间解耦,一个stream中间处理逻辑,无需关注上游与下游的内容,只需要按约定实现自身逻辑即可
  • 效率高:并行流场景效率会比迭代器逐个循环更高
  • 函数式接口,延迟执行的特性,中间管道操作不管有多少步骤都不会立即执行,只有遇到终止操作的时候才会开始执行,可以避免一些中间不必要的操作消耗

6.2 Stream的缺点

Stream也不全是优点,在有些方面也有其弊端:

  • 调试不便代码调测debug不便
  • 需要时间适应程序员从历史写法切换到Stream时,需要一定的学习、适应时间

以上即为java1.8版本Stream处理集合等数据的方法介绍,篇幅较长,仅供参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/49023.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

理解进程status的二进制位表示及进程等待(是什么,为什么,怎么办)

信号编号&#xff1a;低7位 状态编号&#xff1a;次低8位 1.子进程退出后会变为僵尸进程&#xff0c;将退出结果写入自身的task_struct结构体中 2.wait/waitpid是一个系统调用->OS可以读取子进程的task_struct 1.为什么要进行进程等待&#xff1f; 1.将子进程&#xff…

C语言 | Leetcode C语言题解之第241题为运算表达式设计优先级

题目&#xff1a; 题解&#xff1a; #define ADDITION -1 #define SUBTRACTION -2 #define MULTIPLICATION -3int* diffWaysToCompute(char * expression, int* returnSize) {int len strlen(expression);int *ops (int *)malloc(sizeof(int) * len);int opsSize 0;for (in…

单周期CPU(三)译码模块(minisys)(verilog)(vivado)

timescale 1ns / 1ps //module Idecode32 (input reset,input clock,output [31:0] read_data_1, // 输出的第一操作数output [31:0] read_data_2, // 输出的第二操作数input [31:0] Instruction, // 取指单元来的指令input [31:0] …

前台文本直接取数据库值doFieldSQL插入SQL

实现功能&#xff1a;根据选择的车间主任带出角色。 实现步骤&#xff1a;OA的“字段联动”功能下拉选项带不出表“hrmrolemembers”&#xff0c;所以采用此方法。 doFieldSQL("select roleid from HrmResource as a inner join hrmrolemembers as b on a.id b.resource…

快速排序【示例】

冒泡排序可以说是我们学习的第一个真正的排序算法&#xff0c;并且解决了桶排序浪费 空间的问题&#xff0c;但在算法的执行效率上却牺牲了很多&#xff0c;它的时间复杂度达到了 O(N^2)。假如我 们的计算机每秒钟可以运行 10 亿次&#xff0c;那么对 1 亿个数进行排序&#xf…

【京存】助力《抓娃娃》后期制作!

沈腾马丽合体爆改偷感夫妇&#xff0c;暑期开大贴脸开笑!!西虹市IP爆笑回归!! 困苦的爹&#xff0c;辛劳的妈&#xff0c;破烂的院子&#xff0c;破碎的他。西虹市做大做强的路上怎么把老马家落下了?!!! “汤里没油&#xff0c;兜里没子”的马成钢(沈腾 饰)和春兰(马丽 饰)&…

收银系统源码-线上商城diy装修

线下线上一体化收银系统越来越受门店重视&#xff0c;尤其是连锁多门店&#xff0c;想通过线下线上相互带动&#xff0c;相互引流&#xff0c;提升门店营业额。商城商城如何装修呢&#xff1f; 1.收银系统开发语言 核心开发语言: PHP、HTML5、Dart后台接口: PHP7.3后合管理网…

使用Django Rest Framework构建API

Django Rest Framework (DRF) 是一个强大且灵活的工具集&#xff0c;用以构建Web API。它基于Django&#xff0c;一个非常流行的Python Web框架。在本文中&#xff0c;我们将深入探讨如何使用DRF来构建一个高效、结构化的API。 目录 使用Django Rest Framework构建API 一、环…

【SQLServer】如何设计日增几十万数据量的业务分库分表方案

随着公司的业务发展不断的壮大&#xff0c;像一些核心的业务&#xff08;如订单&#xff09;数据量会越来越大&#xff0c;此时就需要考虑分库分表方案来应对业务的发展。今天就来聊聊分库分表的一些设计方案。 1、冷热数据分离方案 在我们业务中有些数据只是最近一段时间使用…

世平基于 NXP UWB Digital-Key Kit 应用方案

大联大世平集团针对汽车数字钥匙&#xff0c;推出了基于 NXP UWB Digital-Key Kit 解决方案。此方案基于超宽带&#xff08;UWB&#xff09;技术&#xff0c;利用 UWB 技术的高精度定位和距离测量能力&#xff0c;实现了安全、便捷的数字钥匙功能。该套件主要器件有 NXP 的 UWB…

14、如何⽤DDD设计微服务代码模型

在完成领域模型设计后&#xff0c;接下来我们就可以开始微服务的设计和 落地了。在微服务落地前&#xff0c;⾸先要确定微服务的代码结构&#xff0c;也就是我 下⾯要讲的微服务代码模型。 只有建⽴了标准的微服务代码模型和代码规范后&#xff0c;我们才可以将 领域对象映射到…

AgentMD:通过大规模临床工具学习提升语言代理的风险预测能力

人工智能咨询培训老师叶梓 转载标明出处 临床计算器在医疗保健中扮演着至关重要的角色&#xff0c;它们通过提供准确的基于证据的预测来辅助临床医生进行诊断和预后评估。然而&#xff0c;由于可用性挑战、传播不畅和功能受限&#xff0c;这些工具的广泛应用常常受限。为了克服…

Django视图与URLs路由详解

在Django Web框架中&#xff0c;视图&#xff08;Views&#xff09;和URLs路由&#xff08;URL routing&#xff09;是Web应用开发的核心概念。它们共同负责将用户的请求映射到相应的Python函数&#xff0c;并返回适当的响应。本篇博客将深入探讨Django的视图和URLs路由系统&am…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第四十八章 Platform 设备驱动

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

java的DOS命令

目录 1.DOS命令了解 DOS介绍 常用的dos命令1 DOS的基本原理 相对路径与绝对路径 常用的dos命令2 2.本章作业 1.编写hello&#xff0c;world程序 2.输出个人基本信息 3.jdk&#xff0c;jre&#xff0c;jvm关系 4.环境变量path配置及作用 5.java编写步骤 6.java编写7…

昇思25天学习打卡营第4天 | 网络构建

在学习和实践MindSpore神经网络模型构建的过程中&#xff0c;我深刻理解了MindSpore中如何通过nn.Cell类来构建和管理复杂的神经网络模型。通过这次的实践&#xff0c;我对神经网络的基本构建和应用有了更加全面的认识&#xff0c;以下是我学习过程中所总结的几点心得&#xff…

科普文:云计算服务类型IaaS, PaaS, SaaS, BaaS, Faas说明

概叙 基本概念 IaaS, PaaS, SaaS, BaaS, 和 FaaS 是云计算服务的不同类型&#xff0c;‌它们各自提供了不同的服务层次和功能。‌ IaaS (Infrastructure as a Service基础设施即服务) 提供基础设施服务&#xff0c;‌包括服务器、‌存储、‌网络等硬件资源。‌用户可以在这些…

Linux嵌入式学习——数据结构——概念和Seqlist

数据结构 相互之间存在一种或多种特定关系的数据元素的集合。 逻辑结构 集合&#xff0c;所有数据在同一个集合中&#xff0c;关系平等。 线性&#xff0c;数据和数据之间是一对一的关系。数组就是线性表的一种。 树&#xff0c; 一对多 图&#xff0c;多对多 …

项目策划不再愁,可道云teamOS流程图助你轻松上阵

在当今这个快节奏、高协同的工作环境中&#xff0c;每一项任务的推进都离不开清晰、高效的沟通与规划。 在线流程图工具&#xff0c;作为数字时代团队协作的得力助手&#xff0c;以其直观易懂的呈现方式、灵活多变的编辑功能&#xff0c;极大地简化了复杂项目的策划与执行流程…

ip地址设置了重启又改变了怎么回事

在数字世界的浩瀚星海中&#xff0c;IP地址就如同每个设备的“身份证”&#xff0c;确保它们在网络中准确无误地定位与通信。然而&#xff0c;当我们精心为设备配置好IP地址后&#xff0c;却时常遭遇一个令人费解的现象&#xff1a;一旦设备重启&#xff0c;原本设定的IP地址竟…