文章目录
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?
三种方式:
- 通过
java.util.Collection.stream()
方法用集合创建流。 - 通过
java.util.Arrays.stream(T[] array)
方法用数组创建流。 - 使用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);
// 输出
总价格之和-方式1:Optional[6711000]
总价格之和-方式2:6711000
总价格之和-方式3:6711000
最高售价-方式1:1250000
最高售价-方式2:1250000
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的复杂度,为后期的系统庞大之后的分库分表做准备。