jdk1.8 List集合Stream流式处理

news2024/11/25 10:32:03

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 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 数组创建流

        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)创建流
		// 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创建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>
        // 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方法创建文本文件的流
        // 逐行读取,创建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

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

3.2 映射(Map)

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

        // 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将这些流“扁平化”成一个流。

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>() {
            @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排序去重

        // 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)搭配实现分页

        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 takeWhile

m.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.不修改元素类型,可做完若干中间操作后,汇聚成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()

 		// 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重复问题,重复时,取最新的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 规约(此处只讨论简单使用,重载方法待研究)

     	// 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()可以返回一个收集器,它计算流中的元素数量。

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

4.1.7 汇总求和(Summing)

        // 获取单个字段的和summingInt
        Integer 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.简单转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
        // 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 -> 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

        // 常用于字符串拼接
        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收集转成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()来创建自定义的收集器。

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有一个满足条件就返回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

        // 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() 来获取一个并行流。

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.coloradmin.cn/o/1938998.html

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

相关文章

单链表的创建与遍历--C

基本结构声明 struct node{int data; //数据域struct node *next;//指针域 }; #include<stdio.h> #include<stdlib.h>struct node{//链表结点 int data;//数据域 struct node *next;//指针域 }; typedef struct node Node; int main(void){Node *head,*p,*…

【高数复盘】武忠祥高数辅导讲义+严选题错题一轮复盘

第一章 函数、极限和连续 高等数学辅导讲义 1. 复盘&#xff1a;(xsinxcosx)(x-sixcosx)&#xff0c;前者可以带入cosx1&#xff0c;而后者不能带入&#xff0c;为何&#xff1f; 2. 复盘&#xff1a; 这道题很明显an≤1&#xff0c;对于直接求极限&#xff0c;可以考虑夹逼…

华为防火墙总部与分支机构建立IPsec VPN涉及NAT穿越

一、IPsec VPN基本概念 1、隧道建立方式&#xff1a;分为手动建立和IKE自动协商&#xff0c;手动建立需要人为配置指定所有IPsec建立的所有参数信息&#xff0c;不支持为动态地址的发起方&#xff0c;实际网络中很少应用&#xff1b;IKE协议是基于密钥管理协议ISAKMP框架设计而…

linux系统设置开机启动的两种方法systemd及rc.local(手工写sh脚本,手工写service服务)

文章目录 知识点实验一、systemd&#xff08;一&#xff09;自写一个sh脚本并加入开机启动&#xff08;二&#xff09;源码安装的nginx加入开机启动 rc.local 知识点 在Linux系统中&#xff0c;有多种方法可以设置开机启动。以下是其中的一些主要方法&#xff1a; systemd 在较…

本地部署 mistralai/Mistral-Nemo-Instruct-2407

本地部署 mistralai/Mistral-Nemo-Instruct-2407 1. 创建虚拟环境2. 安装 fschat3. 安装 transformers4. 安装 flash-attn5. 安装 pytorch6. 启动 controller7. 启动 mistralai/Mistral-Nemo-Instruct-24078. 启动 api9. 访问 mistralai/Mistral-Nemo-Instruct-2407 1. 创建虚拟…

[图解]《分析模式》漫谈16-“我用的”不能变成“我的”

1 00:00:00,720 --> 00:00:02,160 今天&#xff0c;我们来说一下 2 00:00:02,170 --> 00:00:04,850 “我用的”不能变成“我的” 3 00:00:04,860 --> 00:00:11,390 《分析模式》的前言 4 00:00:12,260 --> 00:00:13,410 有这么一句话 5 00:00:14,840 --> 0…

postman接口测试实战篇

击杀小游戏接口测试 接口测试简单介绍击杀小游戏代码下载单接口测试(postman)接口关联并参数化接口测试简单介绍 首先思考两个问题:1.接口是什么?2.接口测试是什么? 1.我们总是把接口想的很复杂,其实呢,它就是一个有特定输入和输出参数的交互逻辑处理单元,它不需要知…

学并发编程前需要明确的一些基础知识

线程和进程的区别 在计算机科学中&#xff0c;线程和进程是两个非常重要的概念。虽然它们常常被一起提到&#xff0c;但它们实际上有很大的不同。作为一个开发者&#xff0c;我在日常工作中经常需要区分这两者&#xff0c;以便更好地进行资源管理和优化。 进程与线程的基本定…

如何解决微服务下引起的 分布式事务问题

一、什么是分布式事务&#xff1f; 虽然叫分布式事务&#xff0c;但不是一定是分布式部署的服务之间才会产生分布式事务。不是在同一个服务或同一个数据库架构下&#xff0c;产生的事务&#xff0c;也就是分布式事务。 跨数据源的分布式事务 跨服务的分布式事务 二、解决方…

华为机试HJ60查找组成一个偶数最接近的两个素数

华为机试HJ60查找组成一个偶数最接近的两个素数 题目&#xff1a; 想法&#xff1a; 构建一个判断是否为素数的函数&#xff0c;再构建一个函数输出构成输入数值相差最小的两个素数。为了保证两个素数相差最小&#xff0c;从输入数值的二分之一处开始判断&#xff0c;遍历得到…

用Python写一个视频采集脚本,对某网站进行批量采集

最近某牙上又出现一批高质量视频&#xff0c;听说删的很快&#xff0c;还好我会Python&#xff0c;赶紧采集下来保存&#xff01; 准备工作 环境使用 Python 3.10 解释器 Pycharm 编辑器 模块使用 requests >>> 数据请求模块 re <正则表达式模块> os <文…

HW行动在即,邮件系统该怎么防守?

1. 什么是HW行动&#xff1f; HW行动是一项由公安部牵头&#xff0c;旨在评估企事业单位网络安全防护能力的活动&#xff0c;是国家应对网络安全问题所做的重要布局之一。 具体实践中&#xff0c;公安部组织攻防红、蓝两队&#xff08;红队为攻击队&#xff0c;主要由“国家队…

【漏洞复现】Netgear WN604 downloadFile.php 信息泄露漏洞(CVE-2024-6646)

0x01 产品简介 NETGEAR WN604是一款由NETGEAR&#xff08;网件&#xff09;公司生产的无线接入器&#xff08;或无线路由器&#xff09;提供Wi-Fi保护协议&#xff08;WPA2-PSK, WPA-PSK&#xff09;&#xff0c;以及有线等效加密&#xff08;WEP&#xff09;64位、128位和152…

面向初学者和专家的 40 大机器学习问答(2024 年更新)

面向初学者和专家的 40 大机器学习问答(2024 年更新) 一、介绍 机器学习是人工智能的重要组成部分,目前是数据科学中最受欢迎的技能之一。如果你是一名数据科学家,你需要擅长 python、SQL 和机器学习——没有两种方法。作为 DataFest 2017 的一部分,我们组织了各种技能测…

正则表达式(Ⅰ)——基本匹配

学习练习建议 正则表达式用途非常广泛&#xff0c;各种语言中都能见到它的身影&#xff08;js&#xff0c;java&#xff0c;mysql等&#xff09; 正则表达式可以快读校验/生成/替换符合要求的模式的字符串&#xff0c;而且语法通俗易懂&#xff0c;所以应用广泛 学习链接&am…

php随机海量高清壁纸系统源码,数据采集于网络,使用很方便

2022 多个分类随机海量高清壁纸系统源码&#xff0c;核心文件就两个&#xff0c;php文件负责采集&#xff0c;html负责显示&#xff0c;很简单。做流量工具还是不错的。 非第三方接口&#xff0c;图片数据采集壁纸多多官方所有数据&#xff01; 大家拿去自行研究哈&#xff0…

WEB前端09-前端服务器搭建(Node.js/nvm/npm)

前端服务器的搭建 在本文中&#xff0c;我们将介绍如何安装和配置 nvm&#xff08;Node Version Manager&#xff09;以方便切换不同版本的 Node.js&#xff0c;以及如何设置 npm&#xff08;Node Package Manager&#xff09;使用国内镜像&#xff0c;并搭建一个简单的前端服…

Merge-On-Read

基本介绍 Iceberg的Merge-On-Read Merge-On-Read&#xff0c;顾名思义&#xff0c;就是在读取的时候进行合并&#xff0c;是与Copy-On-Write相反的一种模式 在Iceberg中&#xff0c;Merge-On-Read同样用于行级更新&#xff0c;整体过程如下 当更新数据时&#xff0c;Iceber…

巴黎奥运会倒计时 一个非常不错的倒计时提醒

巴黎奥运会还有几天就要开幕了&#xff0c;大家应该到处都可以看到巴黎奥运会的倒计时&#xff0c;不管是电视上&#xff0c;还是网络里&#xff0c;一搜索奥运会&#xff0c;就会看到。倒计时其实是一个我们在生活中很常用的一个方法&#xff0c;用来做事情的提醒&#xff0c;…

【学习笔记】无人机系统(UAS)的连接、识别和跟踪(九)-无人机区域地面探测与避让(DAA)

引言 3GPP TS 23.256 技术规范&#xff0c;主要定义了3GPP系统对无人机&#xff08;UAV&#xff09;的连接性、身份识别、跟踪及A2X&#xff08;Aircraft-to-Everything&#xff09;服务的支持。 3GPP TS 23.256 技术规范&#xff1a; 【免费】3GPPTS23.256技术报告-无人机系…