jdk1.8 List集合Stream流式处理

jdk1.8 List集合Stream流式处理

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

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

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

1.1 什么是 Stream?

Stream(流)是 Java 8 引入的一个关键抽象概念,它代表了一个来自数据源的元素队列并支持聚合操作。和迭代器(Iterator)不同,Stream 不存储元素;它们是源到聚合操作的中间桥梁,其操作的执行是延迟的,即只有在需要结果时才执行。

在日常编程中,会经常进行数据(如List、数组)的处理,在没有stram流时,我们的一般操作方式显得比较臃肿,不够优雅简洁。代码如下(原写法):

java 复制代码
        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流式写法

java 复制代码
        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的方法:

方法类型 示例代码 描述
集合转Stream List<String> list = Arrays.asList("a", "b", "c");<br>Stream<String> stream = list.stream(); 通过Collection接口(如List、Set等)的stream()方法创建Stream。
数组转Stream String[] 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接受一个初始值和一个函数,每次迭代时将当前值作为参数传递给函数,返回的结果作为下一次迭代的值。
空Stream Stream<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()方法

java 复制代码
    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 keySet
        map.keySet().stream().forEach(System.out::println);
        map.keySet().stream().forEach(p -> System.out.println(p + "= " + map.get(p)));
        // 2.2 entrySet
        map.entrySet().stream().forEach(System.out::println);
    }

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

2.2 数组创建流

java 复制代码
        int[] array = {5,6,8,7,5,6,1,0};
        // 1.从整数数组创建IntStream
        IntStream intStream = Arrays.stream(array);

        String[] strArray = {"东邪","西毒","南帝","北丐","中神通"};
        // 2.从字符串数组创建Stream
        Stream<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)创建流
java 复制代码
		// 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)创建流
java 复制代码
        // generate创建stream
        Stream<Integer> randomIntStream = Stream.generate(() -> new Random().nextInt(100));
        randomIntStream.limit(10).forEach(System.out::println);

2.4 Stream.builder

  • Stream.builder创建Stream<Integer>
java 复制代码
        // 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>
java 复制代码
        // 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方法创建文本文件的流
java 复制代码
        // 逐行读取,创建Stream
        String 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

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

java 复制代码
{
		...
        // 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());
		...
}
	// 示例:isOverN
    private static boolean isOverN(User p) {
        return p.getAge() > 18;
    }

3.2 映射(Map)

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

java 复制代码
        // 1.装换成简单数据集合,如String
        List<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将这些流"扁平化"成一个流。

java 复制代码
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的用法非常多。

java 复制代码
        // 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>() {
            @Override
            public 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排序去重

java 复制代码
        // 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>() {
                    @Override
                    public 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)搭配实现分页

java 复制代码
        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)实现中间操作的循环操作

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

m.out.println(p.getName())).collect(Collectors.toList());

3.8 mapToInt、mapToDouble ...

java 复制代码
		// 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等数据)

java 复制代码
		// 循环逐个元素
        userList.stream().forEach(System.out::println);

4.1 收集(Collect)

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

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

4.1.1 转换为List

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

java 复制代码
        // 1.不修改元素类型,可做完若干中间操作后,汇聚成list
        List<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()

java 复制代码
 		// 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可以将流中的元素根据某些属性进行分组。

java 复制代码
		// 分组 单个字段可简写,多字段组合如下所示
        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写法

java 复制代码
        // 1.key不重复且value不为null的情况下,可用如下简单写法(为了保险起见,不推荐这种写法)
        Map<String, User> collect1 = userList.stream().collect(Collectors.toMap(User::getName, p -> p));
        // 2.处理key重复问题,重复时,取最新的value
        Map<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 规约(此处只讨论简单使用,重载方法待研究)

java 复制代码
     	// 1.不指定初始值返回Optional
        Optional<Integer> reduceSum = userList.stream().map(User::getAge).collect(Collectors.reducing(Integer::sum));
        // 2.指定初始值,返回int
        Integer 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()可以返回一个收集器,它计算流中的元素数量。

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

4.1.7 汇总求和(Summing)

java 复制代码
        // 获取单个字段的和summingInt
        Integer ageSum = userList.stream().collect(Collectors.summingInt(User::getAge));

4.1.8 汇总对象(IntSummaryStatistics)

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

java 复制代码
        // 统计对象 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

java 复制代码
        // 根据某个字段取最小值(最大值等)对应的元素 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实例,流中的元素将被添加到这个实例中。

java 复制代码
        // toCollection 常与TreeSet去重使用
        // 1.简单转Set
        TreeSet<User> userTreeSet = userList.stream().collect(Collectors.toCollection(TreeSet::new));
        // 2.基本类型转Set
        TreeSet<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>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getName().compareTo(o2.getName());
            }
        })));

4.1.11 mapping

  • 简单使用mapping
java 复制代码
        // 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元素的类型,复杂对象的属性转换和收集
java 复制代码
// 		2.与groupBy搭配使用,分组后,变换元素类型 User -> Address
        Map<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

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

4.1.13 partitioningBy

java 复制代码
        // 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

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

java 复制代码
                // 1.常用于去重后TreeSet转成List
        // 解析 通过Collectors.toCollection收集TreeSet排序去重,然后通过collectingAndThen收集转成List
        List<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()来创建自定义的收集器。

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

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

4.3 归约(Reduce)

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

java 复制代码
        // 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

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

4.5 匹配 anyMatch、allMatch、noneMatch

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

4.6 count max min

java 复制代码
        // 1.获取流中元素的个数,如果List,则与list.size()等效
        long count = userList.stream().count();
        // 2.根据age字段获取age最大值对应的对象,返回Optional,可用orElse规避null
        User 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() 来获取一个并行流。

java 复制代码
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处理集合等数据的方法介绍,篇幅较长,仅供参考。

相关推荐
hanbarger3 分钟前
mybatis框架——缓存,分页
java·spring·mybatis
cdut_suye10 分钟前
Linux工具使用指南:从apt管理、gcc编译到makefile构建与gdb调试
java·linux·运维·服务器·c++·人工智能·python
苹果醋322 分钟前
2020重新出发,MySql基础,MySql表数据操作
java·运维·spring boot·mysql·nginx
小蜗牛慢慢爬行24 分钟前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
azhou的代码园27 分钟前
基于JAVA+SpringBoot+Vue的制造装备物联及生产管理ERP系统
java·spring boot·制造
wm10431 小时前
java web springboot
java·spring boot·后端
smile-yan1 小时前
Provides transitive vulnerable dependency maven 提示依赖存在漏洞问题的解决方法
java·maven
老马啸西风1 小时前
NLP 中文拼写检测纠正论文-01-介绍了SIGHAN 2015 包括任务描述,数据准备, 绩效指标和评估结果
java
Earnest~1 小时前
Maven极简安装&配置-241223
java·maven
皮蛋很白1 小时前
Maven 环境变量 MAVEN_HOME 和 M2_HOME 区别以及 IDEA 修改 Maven repository 路径全局
java·maven·intellij-idea