- Java8提供的Stream支持两种操作
- 一种是中间操作,如filter, map, skip, limit...
- 另外一种是终端操作,如count, findFirst, forEach和reduce...
中间操作不会消耗流,只是将一个流转换成另外一个流,类似于流水线。
而终端操作会消耗流,以产生一个最终结果,终端操作完成后,流就被消耗了,不可再调用相关操作流的方法。
Collectors
预定义好的部分终端操作
一、 规约与汇总
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> op
即R 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只有
true
和false
。其他用法与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": "韩国,日本" }