Java8 Stream流的终端操作

简介: Java8 Stream流的终端操作
  • Java8提供的Stream支持两种操作
  • 一种是中间操作,如filter, map, skip, limit...
  • 另外一种是终端操作,如count, findFirst, forEach和reduce...


中间操作不会消耗流,只是将一个流转换成另外一个流,类似于流水线。

终端操作会消耗流,以产生一个最终结果,终端操作完成后,流就被消耗了,不可再调用相关操作流的方法。

  • Collectors预定义好的部分终端操作


image.png

一、 规约与汇总



1. 查找流中的最大值和最小值

public static void main(String[] args) {
    Random random = new Random();
    Optional<Integer> minVal = Stream.generate(() -> random.nextInt(1000))
            .limit(100)
            .collect(Collectors.minBy(Comparator.comparingInt(x -> x)));
    minVal.ifPresent(System.out::println);
    Optional<Integer> maxVal = Stream.generate(() -> random.nextInt(1000))
            .limit(100)
            .collect(Collectors.maxBy(Comparator.comparingInt(x -> x)));
    maxVal.ifPresent(x -> System.out.println("生成的最大随机值为: " + x));
    //或或或或或或或或或或或或或或或或或或或或
    Optional<Integer> maxVal2 = Stream.generate(() -> random.nextInt(1000))
            .limit(100)
            .max(Comparator.comparingInt(x -> x));
}


2. 求和,计算平均值与结果收集器

public void sumAvg() {
    Random random = new Random();
    // 生成1000范围内的数字的方法
    Supplier<Integer> integerSupplier = () -> random.nextInt(1000);
    // 生成1000个1000以内的数字,并返回一个列表
    List<Integer> integerList = Stream.generate(integerSupplier)
            .limit(1000)
            .collect(Collectors.toList());
    // 求和操作
    Integer sum = integerList.stream()
            .collect(Collectors.summingInt(x -> x));
    System.out.println("求和结果: " + sum);
    // 计算平均值操作
    Double avg = integerList.stream()
            .collect(Collectors.averagingDouble(x -> x));
    System.out.println("平均值为: " + avg);
}
  • 结果:

求和结果: 514905
平均值为: 514.905


  • 在需要同时获取流中元素的个数,求和,平均值,最大值,最小值时,可使用收集器XxxSummaryStatistics

DoubleSummaryStatistics summaryStatistics = integerList.stream()
        .collect(Collectors.summarizingDouble(x -> x));
long count = summaryStatistics.getCount();
double average = summaryStatistics.getAverage();
double max = summaryStatistics.getMax();
double min = summaryStatistics.getMin();
double sumResult = summaryStatistics.getSum();
System.out.println(count);
System.out.println(average);
System.out.println(max);
System.out.println(min);
System.out.println(sumResult);
// XxxSummaryStatistics重写了toString()方法
System.out.println(summaryStatistics);
  • 结果:

1000
514.905
999.0
0.0
514905.0
DoubleSummaryStatistics{count=1000, sum=514905.000000, min=0.000000, average=514.905000, max=999.000000}


3. 连接并返回字符串Collectors.join(delimiter)

public void joinDemo() {
    AppleStream chinaApple = new AppleStream(10, "中国");
    AppleStream usApple = new AppleStream(20, "米国");
    AppleStream koreaApple = new AppleStream(30, "韩国");
    String joinResult = Stream.of(chinaApple, usApple, koreaApple)
            // 需要将流转换成Stream<String>
            .map(AppleStream::toString)
            .collect(Collectors.joining(",", "【", "】"));
    System.out.println(joinResult);
    System.out.println(IntStream.rangeClosed(1, 20)
            .mapToObj(String::valueOf)
            .collect(Collectors.joining(",", "", "")));
}
  • 结果

【AppleStream(weight=10, country=中国),AppleStream(weight=20, country=米国),AppleStream(weight=30, country=韩国)】
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20


4. 自定义规约操作:广义上的规约汇总


  • Collectors.reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> op)
  • 参数U identity 规约起始值,当流中没有元素时也会返回这个值。
  • 参数Function<? super T, ? extends U> mapper 在执行参数3方法之前对流中的元素进行的操作。
  • 参数BinaryOperator<U> opR apply(T t, U u),接收两个参数,并返回经过处理之后的值。

public void reduceDemo() {
    Apple chinaApple = new Apple(10, "中国");
    Apple usApple = new Apple(20, "米国");
    Apple koreaApple = new Apple(30, "韩国");
    //重量最大的苹果
    Optional<Apple> maxWeightApple = Stream.<Apple>of(chinaApple, usApple, koreaApple)
            .collect(Collectors.reducing((x1, x2) -> x1.getWeight() > x1.getWeight() ? x1 : x2));
    maxWeightApple.ifPresent(System.out::println);
    // 最小的重量
    Integer minVal = Stream.of(chinaApple, usApple, koreaApple)
            .collect(Collectors.reducing(0, Apple::getWeight, Integer::min));
    System.out.println(minVal);
    // 质量总和
    Integer sumVal = Stream.of(chinaApple, usApple, koreaApple)
            .collect(Collectors.reducing(0, Apple::getWeight, Integer::sum));
    System.out.println(sumVal);
}


二、 分组


  • Collectors.groupingBy()1. 分组
  • demo(一级分组): 根据规则 质量<=20为优秀,质量>20为一般 将apple进行分组。

/**
 * 质量标准
 */
enum Quality {
    /**
     * 上乘
     */
    PERFECT,
    /**
     * 普通
     */
    NORMAL;
}
public void groupByDemo() {
    Apple chinaApple = new Apple(10, "中国");
    Apple usApple = new Apple(20, "米国");
    Apple koreaApple = new Apple(30, "韩国");
    Apple japanApple = new Apple(40, "日本");
    Map<Quality, List<Apple>> appleQualityMap = Stream.of(chinaApple, usApple, koreaApple, japanApple)
            .collect(Collectors.groupingBy(curApple -> {
                // 质量<=20为优秀,质量>20为一般
                if (curApple.getWeight() <= 20) {
                    return Quality.PERFECT;
                } else {
                    return Quality.NORMAL;
                }
            }));
    System.out.println(JSON.toJSONString(appleQualityMap, true));
}
  • 结果:

{
  "NORMAL": [
    {
      "country": "韩国",
      "weight": 30
    },
    {
      "country": "日本",
      "weight": 40
    }
  ],
  "PERFECT": [
    {
      "country": "中国",
      "weight": 10
    },
    {
      "country": "米国",
      "weight": 20
    }
  ]
}
  • 多级分组
  • 先根据质量进行分组,再根据国家进行分组

public void groupByDemo() {
    Apple chinaApple = new Apple(10, "中国");
    Apple chinaAppleEnhance = new Apple(100, "中国");
    Apple chinaAppleDoubleEnhance = new Apple(1000, "中国");
    Apple usApple = new Apple(20, "米国");
    Apple koreaApple = new Apple(30, "韩国");
    Apple japanApple = new Apple(40, "日本");
    Apple japanAppleEnhance = new Apple(80, "日本");
    Apple japanAppleDoubleEnhance = new Apple(120, "日本");
    Map<Quality, Map<String, List<Apple>>> appleQualityMap = Stream.of(chinaApple, chinaAppleEnhance, chinaAppleDoubleEnhance, usApple, koreaApple, japanApple, japanAppleEnhance, japanAppleDoubleEnhance)
            .collect(Collectors.groupingBy(curApple -> {
                // 质量<=20为优秀,质量>20为一般
                if (curApple.getWeight() <= 20) {
                    return Quality.PERFECT;
                } else {
                    return Quality.NORMAL;
                }
                // 再根据质量进行分组后再根据国家进行分组
            }, Collectors.groupingBy(Apple::getCountry)));
    System.out.println(JSON.toJSONString(appleQualityMap, true));
}
  • 结果

{
  "NORMAL": {
    "韩国": [
      {
        "country": "韩国",
        "weight": 30
      }
    ],
    "中国": [
      {
        "country": "中国",
        "weight": 100
      },
      {
        "country": "中国",
        "weight": 1000
      }
    ],
    "日本": [
      {
        "country": "日本",
        "weight": 40
      },
      {
        "country": "日本",
        "weight": 80
      },
      {
        "country": "日本",
        "weight": 120
      }
    ]
  },
  "PERFECT": {
    "米国": [
      {
        "country": "米国",
        "weight": 20
      }
    ],
    "中国": [
      {
        "country": "中国",
        "weight": 10
      }
    ]
  }
}


  • 注意:可以无限叠加层N层Map哟。


2. 按子组收集数据(指定用于处理子组数据的函数)


  • 观察源码发现,我们使用最多的接收一个参数的groupingBy()方法其实:

public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier) {
        return groupingBy(classifier, toList());
    }


默认第二个参数是Collectors.toList(),实际上可以替换成我们需要的方法,如计算子组的数量:

public void groupByDemo() {
    Apple chinaApple = new Apple(10, "中国");
    Apple chinaAppleEnhance = new Apple(100, "中国");
    Apple chinaAppleDoubleEnhance = new Apple(1000, "中国");
    Apple usApple = new Apple(20, "米国");
    Apple koreaApple = new Apple(30, "韩国");
    Apple japanApple = new Apple(40, "日本");
    Apple japanAppleEnhance = new Apple(80, "日本");
    Apple japanAppleDoubleEnhance = new Apple(120, "日本");
    Map<Quality, Long> qualityLongMap = Stream.of(chinaApple, chinaAppleEnhance, chinaAppleDoubleEnhance, usApple, koreaApple, japanApple, japanAppleEnhance, japanAppleDoubleEnhance)
            .collect(Collectors.groupingBy(curApple -> {
                // 质量<=20为优秀,质量>20为一般
                if (curApple.getWeight() <= 20) {
                    return Quality.PERFECT;
                } else {
                    return Quality.NORMAL;
                }
                // 再根据质量进行分组后再计算每组的元素的个数
            }, Collectors.counting()));
    System.out.println(JSON.toJSONString(qualityLongMap, true));
}
  • 结果:

{
  "PERFECT": 2,
  "NORMAL": 6
}
  • demo2: 先按质量分组,将子组List中的国家名字拼成String打印出来。

public void groupByDemo() {
    Apple chinaApple = new Apple(10, "中国");
    Apple usApple = new Apple(20, "米国");
    Apple koreaApple = new Apple(30, "韩国");
    Apple japanApple = new Apple(40, "日本");
    Map<Quality, String> qualityCountryMap = Stream.of(chinaApple, usApple, koreaApple, japanApple)
            .collect(Collectors.groupingBy(curApple -> {
                // 质量<=20为优秀,质量>20为一般
                if (curApple.getWeight() <= 20) {
                    return Quality.PERFECT;
                } else {
                    return Quality.NORMAL;
                }
                // 再将相应分组的国家的名字打印出来
            }, Collectors.mapping(Apple::getCountry, Collectors.joining(","))));
    System.out.println(JSON.toJSONString(qualityCountryMap, true));
}
  • 结果:

{
  "PERFECT": "中国,米国",
  "NORMAL": "韩国,日本"
}
  • demo3: 将子组的结果转换成另外一种格式collectingAndThen()


先要求: 先根据apple的weight进行分组,再将每组中weight最大的apple找出来,再返回每组最大的apple的weight,即返回Map<quality, maxWeight>

public void groupByDemo() {
    Apple chinaApple = new Apple(10, "中国");
    Apple usApple = new Apple(20, "米国");
    Apple koreaApple = new Apple(30, "韩国");
    Apple japanApple = new Apple(40, "日本");
    Map<Quality, Integer> qualityCountryMap = Stream.of(chinaApple, usApple, koreaApple, japanApple)
            .collect(Collectors.groupingBy(curApple -> {
                // 质量<=20为优秀,质量>20为一般
                if (curApple.getWeight() <= 20) {
                    return Quality.PERFECT;
                } else {
                    return Quality.NORMAL;
                }
            }, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Apple::getWeight)), (optionalApple) -> optionalApple.get().getWeight())));
    System.out.println(JSON.toJSONString(qualityCountryMap, true));
}
  • 结果:

{
    "NORMAL":40,
    "PERFECT":20
}


三、分区 partitioningBy()



分区是分组的一种特殊情况,分区返回的Map的keys只有truefalse。其他用法与groupingBy基本一致。

public void partitioningByDemo() {
    Apple chinaApple = new Apple(10, "中国");
    Apple usApple = new Apple(20, "米国");
    Apple koreaApple = new Apple(30, "韩国");
    Apple japanApple = new Apple(40, "日本");
    Map<Boolean, String> appleQualityCountryMap = Stream.of(chinaApple, usApple, koreaApple, japanApple)
            //以质量20为分界线进行分区,将分区之后的apple的原产地的国家返回
            .collect(Collectors.partitioningBy(x -> x.getWeight() > 20, Collectors.mapping(Apple::getCountry, Collectors.joining(","))));
    System.out.println(JSON.toJSONString(appleQualityCountryMap, true));
}
  • 结果:

{
  "false": "中国,米国",
  "true": "韩国,日本"
}


> 如何自定义Collector?

相关文章
|
4月前
|
安全 Java API
告别繁琐编码,拥抱Java 8新特性:Stream API与Optional类助你高效编程,成就卓越开发者!
【8月更文挑战第29天】Java 8为开发者引入了多项新特性,其中Stream API和Optional类尤其值得关注。Stream API对集合操作进行了高级抽象,支持声明式的数据处理,避免了显式循环代码的编写;而Optional类则作为非空值的容器,有效减少了空指针异常的风险。通过几个实战示例,我们展示了如何利用Stream API进行过滤与转换操作,以及如何借助Optional类安全地处理可能为null的数据,从而使代码更加简洁和健壮。
119 0
|
1月前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
2月前
|
Java 流计算
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
43 1
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
|
2月前
|
Java Shell 流计算
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
25 1
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
|
3月前
|
存储 Java API
Java——Stream流详解
Stream流是JDK 8引入的概念,用于高效处理集合或数组数据。其API支持声明式编程,操作分为中间操作和终端操作。中间操作包括过滤、映射、排序等,可链式调用;终端操作则完成数据处理,如遍历、收集等。Stream流简化了集合与数组的操作,提升了代码的简洁性
109 11
Java——Stream流详解
|
2月前
|
存储 Java 数据处理
Flink-01 介绍Flink Java 3分钟上手 HelloWorld 和 Stream ExecutionEnvironment DataSet FlatMapFunction
Flink-01 介绍Flink Java 3分钟上手 HelloWorld 和 Stream ExecutionEnvironment DataSet FlatMapFunction
36 1
|
3月前
|
Java API C++
Java 8 Stream Api 中的 peek 操作
本文介绍了Java中`Stream`的`peek`操作,该操作通过`Consumer&lt;T&gt;`函数消费流中的每个元素,但不改变元素类型。文章详细解释了`Consumer&lt;T&gt;`接口及其使用场景,并通过示例代码展示了`peek`操作的应用。此外,还对比了`peek`与`map`的区别,帮助读者更好地理解这两种操作的不同用途。作者为码农小胖哥,原文发布于稀土掘金。
123 9
Java 8 Stream Api 中的 peek 操作
|
3月前
|
Java C# Swift
Java Stream中peek和map不为人知的秘密
本文通过一个Java Stream中的示例,探讨了`peek`方法在流式处理中的应用及其潜在问题。首先介绍了`peek`的基本定义与使用,并通过代码展示了其如何在流中对每个元素进行操作而不返回结果。接着讨论了`peek`作为中间操作的懒执行特性,强调了如果没有终端操作则不会执行的问题。文章指出,在某些情况下使用`peek`可能比`map`更简洁,但也需注意其懒执行带来的影响。
151 2
Java Stream中peek和map不为人知的秘密
|
3月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
|
3月前
|
Java 程序员 API
Java 8新特性之Lambda表达式与Stream API的探索
【9月更文挑战第24天】本文将深入浅出地介绍Java 8中的重要新特性——Lambda表达式和Stream API,通过实例解析其语法、用法及背后的设计哲学。我们将一探究竟,看看这些新特性如何让Java代码变得更加简洁、易读且富有表现力,同时提升程序的性能和开发效率。