Java8 Stream-深入理解筛选、归约、分组、聚合

1. 什么是stream?

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

Stream(流)是一个来自数据源的元素队列并支持聚合操作:

基本概念:

  • 元素:特定类型的对象,形成一个队列。
  • 数据源: 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作: 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

流的操作

  • 中间操作: 每次返回一个新的流,可以有多个,如 foreach / filter 等。
  • 终端操作:每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值,如 max / count 等。

特性:

  • stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
  • stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
  • stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。

2. 如何创建stream?

三种方式:

  1. 通过 java.util.Collection.stream() 方法用集合创建流。
  2. 通过 java.util.Arrays.stream(T[] array)方法用数组创建流。
  3. 使用Stream的静态方法:of()、iterate()、generate()。

具体创建方式如下:

2.1 空的流

// 代码
Stream<String> emptyStream = Stream.empty();
System.out.println(emptyStream);

// 输出
java.util.stream.ReferencePipeline$Head@4554617c

2.2 集合的流

可以创建任何类型的集合(Collection, List, Set)的流:

// 代码
private static void streamByCollection() {
        List<String> list = Arrays.asList("l", "f", "c");
        // 创建顺序流
        Stream<String> stream = list.stream();
        // 创建并行流
        Stream<String> parallelStream = list.parallelStream();

        System.out.println(stream);
        System.out.println(parallelStream);
    }
    
// 输出
java.util.stream.ReferencePipeline$Head@43556938
java.util.stream.ReferencePipeline$Head@3d04a311

2.3 数组的流

// 代码
private static void streamByArrays() {
        long[] arr = {1, 3, 5, 2, 4, 6};
        LongStream stream = Arrays.stream(arr);

        System.out.println(stream);
    }

// 输出
java.util.stream.LongPipeline$Head@3abfe836

2.4 Stream.builder()

使用builder时,应在语句的右侧另外使用的类型,否则build()方法将创建 Stream 的实例:


Stream<String> streamBuilder = Stream.<String>builder().add("L").add("f").add("c").build();

2.5 Stream.generate()

generate()方法接受Supplier 进行元素生成。由于结果流是无限的,因此开发人员应指定所需的大小,否则generate()方法运行后会达到内存的上限:

// 代码
Stream<String> streamGenerated = Stream.generate(() -> "Linfanchen").limit(10);
System.out.println(streamGenerated);

// 输出
java.util.stream.SliceOps$1@6adca536

2.6 Stream.iterate()

// 代码
Stream<Integer> streamIterated = Stream.iterate(66, n -> n + 2).limit(4);
System.out.println(streamIterated);
streamIterated.forEach(System.out::println);

// 输出
java.util.stream.SliceOps$1@3c5a99da
66
68
70
72

2.7 字符串的流

由于JDK中没有接口CharStream,因此使用IntStream表示字符流。用到了String类的chars()方法。

// 代码
IntStream streamOfChars = "Lfc".chars();
System.out.println(streamOfChars);

// 输出
java.util.stream.IntPipeline$Head@6833ce2c

2.8 文件的流

Java NIO类 Files 允许通过lines()方法生成文本文件的Stream 。文本的每一行都成为流的元素:

// 代码
Path path = Paths.get("D:\\study\\javaBase\\oop\\src\\com\\lfc\\stream\\log.txt");
        try {
            Stream<String> streamOfStrings = Files.lines(path);
            Stream<String> streamWithCharset = Files.lines(path, Charset.forName("UTF-8"));

            streamOfStrings.forEach(System.out::println);
            streamWithCharset.forEach(System.out::println);
        } catch (IOException ioe) {
            System.out.println(ioe.getMessage());
        }

// 输出
abc
abc

2.9 基本类型的流

Java 8提供了从三种基本类型中创建流的方式:int,long,double。
由于Stream 是泛型接口,无法将基本类型用作泛型的类型参数,因此创建了三个新的特殊接口:IntStream,LongStream和DoubleStream。使用新接口可以减轻不必要的自动装箱,从而提效率:

// 代码
IntStream intStream = IntStream.range(1, 5);
LongStream longStream = LongStream.rangeClosed(1, 5);
DoubleStream doubleStream = DoubleStream.of(123.45);

System.out.println(intStream);
System.out.println(longStream);
System.out.println(doubleStream);

// 输出
java.util.stream.IntPipeline$Head@2ff5659e
java.util.stream.LongPipeline$Head@77afea7d
java.util.stream.DoublePipeline$Head@161cd475

并行流和顺序流的区别

stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选一组数中的奇数:
在这里插入图片描述

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流

3. 怎么玩转stream?

操作:
流程:创建Stream流 --> 中间操作 --> 终端操作
在这里插入图片描述

  • 无状态:指元素的处理不受之前元素的影响。
  • 有状态:指该操作只有拿到所有元素之后才能继续下去。
  • 非短路操作:指必须处理所有元素才能得到最终结果。
  • 短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。

案例领域模型(应该每个男人都喜欢车吧):

实体类:

/**
 * 汽车实体类
 */
public class Car {

    // 汽车名称
    private String name;

    // 品牌
    private String brand;

    // 设计国家
    private String country;

    // 颜色
    private String color;

    // 寿命
    private int life;

    // 价格
    private int price;

    public Car(String name, String brand, String country, String color, int life, int price) {
        this.name = name;
        this.brand = brand;
        this.country = country;
        this.color = color;
        this.life = life;
        this.price = price;
    }
    // 此处省略getter和setter...
}

业务数据:

private static List<Car> getCars() {
        List<Car> carList = new ArrayList<>();
        carList.add(new Car("325Li M运动曜夜", "宝马", "德国", "矿石白", 15, 370000));
        carList.add(new Car("530Li 尊享型 M运动套装", "宝马", "德国", "碳黑", 16, 520000));
        carList.add(new Car("740Li xDrive 行政型 豪华套装", "宝马", "德国", "波尔蒂芒蓝", 20, 1100000));

        carList.add(new Car("C260L 运动版4MATIC", "奔驰", "德国", "贝母白", 10, 410000));
        carList.add(new Car("E300L 尊贵型", "奔驰", "德国", "曜岩黑", 14, 550000));
        carList.add(new Car("S500L 4MATIC", "奔驰", "德国", "海岳蓝", 18, 1250000));

        carList.add(new Car("ES260 卓越版", "雷克萨斯", "日本", "超音速钛银", 20, 375000));
        carList.add(new Car("ES300h 尊享版", "雷克萨斯", "日本", "凌波玉色", 25, 450000));
        carList.add(new Car("ES300h 行政版", "雷克萨斯", "日本", "超音速石英白", 30, 552000));

        carList.add(new Car("CT5 28T铂金型", "凯迪拉克", "美国", "云海白", 6, 306000));
        carList.add(new Car("CT6 28T尊贵型", "凯迪拉克", "美国", "玛雅黑", 8, 393000));
        carList.add(new Car("XT6 六座四驱豪华型", "凯迪拉克", "美国", "黛蓝", 11, 435000));

        return carList;
    }

3.1 遍历/匹配(foreach/find/match)

Stream也是支持类似集合的遍历和匹配元素的,只是Stream中的元素是以Optional类型存在的。
在这里插入图片描述

        
        List<Integer> list = Arrays.asList(1, 2, 5, 6, 8, 9);

        // 找出偶数
        list.stream().filter(n -> n % 2 == 0).forEach(System.out::println);

        // 找出第一个大于5的偶数
        Optional<Integer> findFirst = list.stream()
                .filter(n -> (n % 2 == 0 && n > 5))
                .findFirst();

        // 找出偶数(通过并行流)
        Optional<Integer> findAny = list.parallelStream().filter(n -> n > 2).findAny();

        // 是否包含满足条件的元素
        boolean anyMatch = list.stream().anyMatch(n -> n > 8);

        System.out.println("\n 第一个大于5的偶数:" + findFirst.get());
        System.out.println("\n 通过并行流找出偶数:" + findAny.toString());
        System.out.println("\n 是否存在大于8的数:" + anyMatch);
        

3.2 筛选(filter)

筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。
在这里插入图片描述

// 代码
List<Car> carList = getCars();

        List<String> filterList = carList.parallelStream()
                .filter(x -> x.getPrice() > 500000)
                .map(Car::getName)
                .collect(Collectors.toList());

        System.out.println("价格高于50w的车:" + filterList);

// 输出
[530Li 尊享型 M运动套装, 740Li xDrive 行政型 豪华套装, E300L 尊贵型, S500L 4MATIC, ES300h 行政版]

3.3 聚合(max/min/count)

类似于MySQL中的聚合计算。
在这里插入图片描述

3.3.1 获取字符串列表中最长的元素

// 代码
List<String> list = Arrays.asList("Java", "php", "c", "c++", "python", "shell", "Go");

Optional<String> maxString = list.parallelStream().max(Comparator.comparing(String::length));
System.out.println("字面最长的开发语言是:" + maxString.get());

// 输出
字面最长的开发语言是:python

3.3.2 获取Integer集合中最大的数

// 代码
List<Integer> list = Arrays.asList(1, 443, 67, 23, 98, 77);
// 自然排序
Optional<Integer> maxNatural = list.parallelStream().max(Integer::compareTo);

// 自定义排序:重写compare()方法
Optional<Integer> maxSelf = list.stream().max(new Comparator<Integer>() {
@Override
   public int compare(Integer o1, Integer o2) {
       return o1.compareTo(o2);
   }
 });
System.out.println("自然排序的最大值是:" + maxNatural.get());
System.out.println("自定义排序的最大值是:" + maxSelf.get());

// 输出
自然排序的最大值是:443
自定义排序的最大值是:443

3.3.3 获取Double列表中最小的数

// 代码
List<Double> list = Arrays.asList(2.2, 443.2, 67.5, 23.1, 98.9, 77.8);

Optional<Double> min = list.parallelStream().min(Double::compareTo);
System.out.println("数组中最小的数值为:" + min.get());

// 输出
数组中最小的数值为:2.2

3.3.4 获取价格高于35w的车数和最贵的车

List<Car> carList = getCars();
// 获取价格高于35w的车数量
long count = carList.stream().filter(car -> car.getPrice() > 350000).count();

// 获取车价最高的车
Optional<Car> maxCar = carList.stream().max(Comparator.comparing(Car::getPrice));

System.out.println("价格高于35w的车数量:" + count);
System.out.println("价格最高的车是:" + maxCar.get().getName());

// 输出
价格高于35w的车数量:11
价格最高的车是:S500L 4MATIC

3.4 映射(map/flatMap)

可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:

  • map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

在这里插入图片描述
在这里插入图片描述

3.4.1 字符串集合元素全都转换成大写

// 代码
String[] strArr = {"Java", "c", "c++", "php", "Python", "shell", "Go"};
List<String> stringList = Arrays.asList(strArr).stream()
                .map(String::toUpperCase)
                .collect(Collectors.toList());
System.out.println("转换成大写之后:" + stringList);

// 输出
转换成大写之后:[JAVA, C, C++, PHP, PYTHON, SHELL, GO]

3.4.2 数字集合元素全部 ×2 + 5 操作

// 代码
List<Integer> list = Arrays.asList(1, 3, 5, 7, 6, 8, 9);
List<Integer> listNew = list.stream()
                .map(n -> n * 2 + 5)
                .collect(Collectors.toList());
System.out.println("对整数列表元素操作后的结果:" + listNew);

// 输出
对整数列表元素操作后的结果:[7, 11, 15, 19, 17, 21, 23]

3.4.3 所有汽车都降价2w

List<Car> carList = getCars();

        // 不改变原汽车集合
        List<Car> carListNew = carList.stream().map(car -> {
            Car carNew = new Car(car.getName(), car.getBrand(), car.getCountry(), car.getColor(), car.getLife(), 0);
            carNew.setPrice(car.getPrice() - 20000);
            return carNew;
        }).collect(Collectors.toList());

        System.out.println("不改变原汽车集合降价:" + carListNew);

        // 改变原汽车集合
        List<Car> carListNew2 = carList.stream().map(car -> {
            car.setPrice(car.getPrice() - 20000);
            return car;
        }).collect(Collectors.toList());
        System.out.println("改变原汽车集合降价:" + carListNew2);

3.4.4 将两个字符数组合并成一个新的字符数组

// 代码
List<String> list = Arrays.asList("J-a-v-a", "1-2-6-7-9");
List<String> listNew = list.stream().flatMap(s -> {
    String[] splitArr = s.split("-");
    Stream<String> s2 = Arrays.stream(splitArr);
    return s2;
}).collect(Collectors.toList());

System.out.println("处理前:" + list);
 System.out.println("处理hou:" + listNew);


// 输出
处理前:[J-a-v-a, 1-2-6-7-9]
处理hou:[J, a, v, a, 1, 2, 6, 7, 9]

3.5 归约(reduce)

把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。
在这里插入图片描述

3.5.1 求Integer集合的元素之和、乘积、最大值

List<Integer> list = Arrays.asList(1, 2, 6, 7, 9, 34, 56, 88);

        // 求和-方式1
        Optional<Integer> sum1 = list.stream().reduce((x, y) -> x + y);

        // 求和-方式2
        Optional<Integer> sum2 = list.stream().reduce(Integer::sum);

        // 求和-方式3
        Integer sum3 = list.stream().reduce(0, Integer::sum);

        // 求积
        Optional<Integer> product = list.stream().reduce((x, y) -> x * y);

        // 求最大值-方式1
        Optional<Integer> max1 = list.stream().reduce((x, y) -> x > y ? x : y);

        // 求最大值-方式2
        Integer max2 = list.stream().reduce(1, Integer::max);

        System.out.println("求和-方式1:" + sum1.get());
        System.out.println("求和-方式2:" + sum2.get());
        System.out.println("求和-方式3:" + sum3);
        System.out.println("求积:" + product.get());
        System.out.println("求最大值-方式1:" + max1.get());
        System.out.println("求最大值-方式2:" + max2);

3.5.2 求所有汽车的售价之和,最高价格

List<Car> carList = getCars();

        // 总价格之和:方式1
        Optional<Integer> sumPrice = carList.stream().map(Car::getPrice).reduce(Integer::sum);

        // 总价格之和:方式2
        Integer sumPrice2 = carList.stream().reduce(0, (sum, car) -> sum += car.getPrice(),
                (sum1, sum2) -> sum1 + sum2);

        // 总价格之和:方式3
        Integer sumPrice3 = carList.stream().reduce(0, (sum, car) -> sum += car.getPrice(), Integer::sum);

        // 最高售价:方式1
        Integer maxPrice1 = carList.stream().reduce(0, (max, car) -> max > car.getPrice() ? max : car.getPrice(),
                Integer::max);

        // 最高售价:方式2
        Integer maxPrice2 = carList.stream().reduce(0, (max, car) -> max > car.getPrice() ? max : car.getPrice(),
                (max1, max2) -> max1 > max2 ? max1 : max2);

        System.out.println("总价格之和-方式1:" + sumPrice);
        System.out.println("总价格之和-方式2:" + sumPrice2);
        System.out.println("总价格之和-方式3:" + sumPrice3);
        System.out.println("最高售价-方式1:" + maxPrice1);
        System.out.println("最高售价-方式2:" + maxPrice2);

// 输出
总价格之和-方式1Optional[6711000]
总价格之和-方式26711000
总价格之和-方式36711000
最高售价-方式11250000
最高售价-方式21250000

3.6 收集(collect)

把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。主要使用java.util.stream.Collectors提供的静态方法。

3.6.1 归集(toList/toSet/toMap)

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList、toSet和toMap比较常用,另外还有toCollection、toConcurrentMap等复杂一些的用法。

List<Integer> list = Arrays.asList(1, 3, 5, 7, 6, 8, 9);
        // toList
        List<Integer> listNew = list.stream().filter(n -> n % 2 == 0).collect(Collectors.toList());
        // toSet
        Set<Integer> set = list.stream().filter(n -> n % 2 == 0).collect(Collectors.toSet());

        // toMap
        List<Car> carList = getCars();
        Map<?, Car> map = carList.stream()
                .filter(car -> car.getPrice() > 500000)
                .collect(Collectors.toMap(Car::getName, car -> car));

        // key和value均为字段
        Map<?, String> map2 = carList.stream()
                .collect(Collectors.toMap(Car::getName, Car::getBrand));

        // 提供了一个取值的方法 (k1,k2) -> k1,如果k1和k2相等,取k1作为key
        Map<?, Car> map31 = carList.stream()
                .filter(car -> { "德国".equals(car.getBrand()); return true; })
                .collect(Collectors.toMap(Car::getName, car -> car, (k1, k2) -> k1));
        // 将前面的value 和后面的value拼接起来;
        Map<?, String> map32 = carList.stream()
                .filter(car -> { "德国".equals(car.getBrand()); return true; })
                .collect(Collectors.toMap(Car::getName, Car::getBrand, (k1, k2) -> k1 + "," + k2));
        // 重复时将重复key的数据组成集合;
        Map<?, List<String>> map33 = carList.stream()
                .filter(car -> { "德国".equals(car.getBrand()); return true; })
                .collect(Collectors.toMap(Car::getName,
                        car -> {
                            List<String> brandList = new ArrayList<>();
                            brandList.add(car.getBrand());
                            return brandList;
                        },
                        (List<String> value1, List<String> value2) -> {
                            value1.addAll(value2);
                            return value1;
                        }
                ));

        // value为null问题。使用filter过滤掉null值
        Map<?, String> map4 = carList.stream()
                .filter(car -> car.getBrand() != null)
                .collect(Collectors.toMap(Car::getName, Car::getBrand));

        System.out.println("List -> List:" + listNew);
        System.out.println("List -> Set:" + set);
        System.out.println("List -> Map:" + map);
        System.out.println("List -> Map:" + map2);
        System.out.println("List -> Map:" + map31);
        System.out.println("List -> Map:" + map32);
        System.out.println("List -> Map:" + map33);
        System.out.println("List -> Map:" + map4);

// 输出
List -> List[6, 8]
List -> Set[6, 8]
List -> Map{E300L 尊贵型=com.lfc.stream.Car@1ddc4ec2, 530Li 尊享型 M运动套装=com.lfc.stream.Car@133314b, ES300h 行政版=com.lfc.stream.Car@b1bc7ed, S500L 4MATIC=com.lfc.stream.Car@7cd84586, 740Li xDrive 行政型 豪华套装=com.lfc.stream.Car@30dae81}
List -> Map{CT6 28T尊贵型=凯迪拉克, ES260 卓越版=雷克萨斯, E300L 尊贵型=奔驰, XT6 六座四驱豪华型=凯迪拉克, C260L 运动版4MATIC=奔驰, 530Li 尊享型 M运动套装=宝马, ES300h 行政版=雷克萨斯, S500L 4MATIC=奔驰, CT5 28T铂金型=凯迪拉克, ES300h 尊享版=雷克萨斯, 740Li xDrive 行政型 豪华套装=宝马, 325Li M运动曜夜=宝马}
List -> Map{CT6 28T尊贵型=com.lfc.stream.Car@1b2c6ec2, ES260 卓越版=com.lfc.stream.Car@4edde6e5, E300L 尊贵型=com.lfc.stream.Car@1ddc4ec2, XT6 六座四驱豪华型=com.lfc.stream.Car@70177ecd, C260L 运动版4MATIC=com.lfc.stream.Car@1e80bfe8, 530Li 尊享型 M运动套装=com.lfc.stream.Car@133314b, ES300h 行政版=com.lfc.stream.Car@b1bc7ed, S500L 4MATIC=com.lfc.stream.Car@7cd84586, CT5 28T铂金型=com.lfc.stream.Car@66a29884, ES300h 尊享版=com.lfc.stream.Car@4769b07b, 740Li xDrive 行政型 豪华套装=com.lfc.stream.Car@30dae81, 325Li M运动曜夜=com.lfc.stream.Car@cc34f4d}
List -> Map{CT6 28T尊贵型=凯迪拉克, ES260 卓越版=雷克萨斯, E300L 尊贵型=奔驰, XT6 六座四驱豪华型=凯迪拉克, C260L 运动版4MATIC=奔驰, 530Li 尊享型 M运动套装=宝马, ES300h 行政版=雷克萨斯, S500L 4MATIC=奔驰, CT5 28T铂金型=凯迪拉克, ES300h 尊享版=雷克萨斯, 740Li xDrive 行政型 豪华套装=宝马, 325Li M运动曜夜=宝马}
List -> Map{CT6 28T尊贵型=[凯迪拉克], ES260 卓越版=[雷克萨斯], E300L 尊贵型=[奔驰], XT6 六座四驱豪华型=[凯迪拉克], C260L 运动版4MATIC=[奔驰], 530Li 尊享型 M运动套装=[宝马], ES300h 行政版=[雷克萨斯], S500L 4MATIC=[奔驰], CT5 28T铂金型=[凯迪拉克], ES300h 尊享版=[雷克萨斯], 740Li xDrive 行政型 豪华套装=[宝马], 325Li M运动曜夜=[宝马]}
List -> Map{CT6 28T尊贵型=凯迪拉克, ES260 卓越版=雷克萨斯, E300L 尊贵型=奔驰, XT6 六座四驱豪华型=凯迪拉克, C260L 运动版4MATIC=奔驰, 530Li 尊享型 M运动套装=宝马, ES300h 行政版=雷克萨斯, S500L 4MATIC=奔驰, CT5 28T铂金型=凯迪拉克, ES300h 尊享版=雷克萨斯, 740Li xDrive 行政型 豪华套装=宝马, 325Li M运动曜夜=宝马}

3.6.2 统计(count/averaging)

  • 计数:count
  • 平均值:averagingInt、 averagingLong、 averagingDouble
  • 最值:maxBy、minBy
  • 求和:summingInt、summingLong、summingDouble
  • 统计以上所有:summarizingInt、summarizingLong、summarizingDouble

统计车子数、平均价格、售价总额、最高售价。

List<Car> carList = getCars();

        // 总数
        long count = carList.stream().collect(Collectors.counting());

        // 汽车平均售价
        Double averagePrice = carList.stream().collect(Collectors.averagingDouble(Car::getPrice));

        // 最高售价
        Optional<Integer> maxPrice = carList.stream().map(Car::getPrice).collect(Collectors.maxBy(Integer::compare));

        // 总售价
        Integer sum = carList.stream().collect(Collectors.summingInt(Car::getPrice));

        // 价格所有信息
        DoubleSummaryStatistics collect = carList.stream().collect(Collectors.summarizingDouble(Car::getPrice));

        System.out.println("总数:" + count);
        System.out.println("汽车平均售价:" + averagePrice);
        System.out.println("最高售价:" + maxPrice);
        System.out.println("总售价:" + sum);
        System.out.println("价格所有信息:" + collect);

输出

总数:12
汽车平均售价:559250.0
最高售价:Optional[1250000]
总售价:6711000
价格所有信息:DoubleSummaryStatistics{count=12, sum=6711000.000000, min=306000.000000, average=559250.000000, max=1250000.000000}

3.6.3 分组(partitioningBy/groupingBy)

  • 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
  • 分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。
    在这里插入图片描述
List<Car> carList = getCars();

        // 将汽车按照售价是否超过50w分区
        Map<Boolean, List<Car>> part = carList.stream()
                .collect(Collectors.partitioningBy(car -> car.getPrice() > 500000));

        // 将汽车按照品牌分组
        Map<String, List<Car>> group = carList.stream().collect(Collectors.groupingBy(Car::getBrand));

        // 将汽车先按颜色分组,再按照品牌分组
        Map<String, Map<String, List<Car>>> group2 = carList.stream()
                .collect(Collectors.groupingBy(Car::getColor, Collectors.groupingBy(Car::getBrand)));

        // 按品牌分组,统计每个组总价格
        Map<String, Integer> groupByBrand1 = carList.stream()
                .collect(Collectors.groupingBy(Car::getBrand, Collectors.summingInt(Car::getPrice)));

        // 按品牌分组,统计每个组总条数
        Map<String, Long> groupByBrand2 = carList.stream()
                .collect(Collectors.groupingBy(Car::getBrand, Collectors.counting()));

        // 统计每个品牌,每个颜色的数量
        Map<String, Map<String, Long>> groupByBrand3 = carList.stream().collect(
                Collectors.groupingBy(
                        Car::getBrand,
                        Collectors.groupingBy(Car::getColor, Collectors.counting())
                )
        );

        System.out.println("将汽车按照售价是否超过50w分区: " + part);
        System.out.println("将汽车按照品牌分组: " + group);
        System.out.println("将汽车先按颜色分组,再按照品牌分组: " + group2);
        System.out.println("按品牌分组,统计每个组总价格: " + groupByBrand1);
        System.out.println("按品牌分组,统计每个组总条数: " + groupByBrand2);
        System.out.println("统计每个品牌,每个颜色的数量: " + groupByBrand3);

3.6.4 接合(joining)

将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。

List<Car> carList = getCars();
        List<String> distinctBrand = carList.stream()
                .map(car -> car.getBrand())
                .distinct()
                .collect(Collectors.toList());
        String brandStr = distinctBrand.stream().collect(Collectors.joining("-"));

        System.out.println("拼接去重后的品牌名称:" + brandStr);
 
// 输出
拼接去重后的品牌名称:宝马-奔驰-雷克萨斯-凯迪拉克

3.6.5 归约(reducing)

Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持。

List<Car> carList = getCars();

        // 每辆车减去购置税25000后的售价总和
        Integer sum = carList.stream().collect(Collectors.reducing(0, Car::getPrice, (x, y) -> (x + y - 25000)));
        System.out.println("每辆车减去购置税25000后的售价总和:" + sum);

        // Stream的reduce方法
        Optional<Integer> sum2 = carList.stream().map(Car::getPrice).reduce(Integer::sum);
        System.out.println("售价总和:" + sum2.get());

3.7 排序(sorted)

可以类比于MySQL的order by

  • sorted():自然排序,流中元素需实现Comparable接口
  • sorted(Comparator com):Comparator排序器自定义排序
List<Car> carList = getCars();

        // 按售价升序
        List<String> priceAscList = carList.stream()
                .sorted(Comparator.comparing(Car::getPrice))
                .map(Car::getName)
                .collect(Collectors.toList());

        // 按售价降序
        List<String> priceDescList = carList.stream()
                .sorted(Comparator.comparing(Car::getPrice).reversed())
                .map(Car::getName)
                .collect(Collectors.toList());

        // 先车寿命再按售价升序
        List<String> lifePriceAscList = carList.stream()
                .sorted(Comparator.comparing(Car::getLife).thenComparing(Car::getPrice))
                .map(Car::getName)
                .collect(Collectors.toList());

        // 先按车寿命再按售价降序(自定义排序)
        List<String> lifePriceDeseBySelfList = carList.stream().sorted((car1, car2) -> {
            if (car1.getLife() == car2.getLife()) {
                return car2.getPrice() - car1.getPrice();
            } else {
                return car2.getLife() - car1.getLife();
            }
        }).map(Car::getName).collect(Collectors.toList());

        System.out.println("按售价升序: " + priceAscList);
        System.out.println("按售价降序: " + priceDescList);
        System.out.println("先车寿命再按售价升序: " + lifePriceAscList);
        System.out.println("先按车寿命再按售价降序(自定义排序): " + lifePriceDeseBySelfList);

3.8 提取/组合 【终端操作】

流也可以进行合并、去重、限制、跳过等操作。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

String[] arr1 = {"p", "y", "t", "h", "o", "n"};
        String[] arr2 = {"J", "a", "v", "a", "e", "e"};

        Stream<String> stream1 = Stream.of(arr1);
        Stream<String> stream2 = Stream.of(arr2);

        // 合并(concat)和去重(distinct)  类比 MySQL 的concat 和 distinct
        List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());

        // 限制获取多少元素(limit) 类比 MySQL 的limit
        List<Integer> collect = Stream.iterate(1, i -> i + 2).limit(5).collect(Collectors.toList());

        // skip 跳过前n个元素 类比 MySQL 的offset
        List<Integer> collect2 = Stream.iterate(1, i -> i + 2).skip(2).limit(5).collect(Collectors.toList());

        System.out.println("合并和去重:" + newList);
        System.out.println("限制:" + collect);
        System.out.println("跳过:" + collect2);

输出:

合并和去重:[p, y, t, h, o, n, J, a, v, e]
限制:[1, 3, 5, 7, 9]
跳过:[5, 7, 9, 11, 13]

4. 总结

Java8的Stream API非常强大,其中的一些操作可与MySQL相关的指令类比学习。阿里巴巴规范说明里联表一般不要超过3个,那么我们可以通过简单的查询然后再在程序进行Stream计算,降低SQL的复杂度,为后期的系统庞大之后的分库分表做准备。

理论部分参考文章(两位写的很好):
云深i不知处
三分恶

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 8引入了Stream API,它是一种处理集合数据的新方式。Stream API提供了一种流式操作的方式,可以对集合进行过滤、映射、排序、聚合等操作,使得代码更加简洁、易读和高效。 Stream是一个来自数据源的元素队列并支持聚合操作。它可以是集合、数组、I/O channel、产生器等。Stream操作可以顺序执行,也可以并行执行。 Java 8 Stream API的特点包括: 1. 延迟执行:Stream操作通常是延迟执行的,只有在终止操作时才会触发实际的计算。 2. 内部迭代:Stream API使用内部迭代的方式,不需要显式地编写循环,使得代码更加简洁。 3. 函数式编程:Stream API支持函数式编程风格,可以通过Lambda表达式来定义操作。 4. 并行处理:Stream API提供了并行处理的能力,可以充分利用多核处理器的优势,提高处理速度。 使用Stream API可以通过一系列的中间操作和终止操作来对集合进行处理。中间操作包括过滤、映射、排序等操作,终止操作包括聚合、收集、遍历等操作。 下面是一些常用的Stream操作方法: 1. filter(Predicate<T> predicate):根据指定条件过滤元素。 2. map(Function<T, R> mapper):将元素进行映射转换。 3. sorted(Comparator<T> comparator):对元素进行排序。 4. distinct():去除重复的元素。 5. limit(long maxSize):限制元素的数量。 6. skip(long n):跳过指定数量的元素。 7. forEach(Consumer<T> action):对每个元素执行指定操作。 8. collect(Collector<T, A, R> collector):将元素收集到集合中。 9. reduce(BinaryOperator<T> accumulator):对元素进行归约操作。 10. parallel():启用并行处理。 以上只是Stream API的一部分常用操作,还有更多的操作方法可以根据具体需求使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值