文章目录
- 1 认识Stream流
- 1.1 什么是流
- 1.2 流与集合
- 1.2.1 流只能遍历一次
- 1.2.2 外部迭代和内部迭代
- 1.3 流操作
- 1.3.1 中间操作
- 1.3.2 终端操作
- 1.3.3 使用流
- 2 学会使用Stream流
- 2.1 筛选和切片
- 2.1.1 用谓词筛选
- 2.1.2 筛选各异的元素
- 2.1.3 截短流
- 2.1.4 跳过元素
- 2.2 映射
- 2.2.1 map方法
- 2.2.2 flatMap方法
- 2.3 查找和匹配
- 2.3.1 检查谓词是否至少匹配一个元素
- 2.3.2 检查谓词是否匹配所有元素
- 2.3.3 查找元素
- 2.3.4 查找第一个元素
- 2.4 归约
- 2.4.1 元素求和
- 2.4.2 最大值和最小值
- 2.5 数值流
- 2.5.1 映射到数值流
- 2.5.2 转换回对象流
- 数值范围
- 2.6 构建流
- 2.6.1 由值创建流
- 2.6.2 由数组创建流
- 2.6.3 由文件生成流
- 2.6.4 由函数生成流:创建无限流
- 3 利用Stream流来处理更为复杂的数据处理查询
- 3.1 collect可以做什么
- 3.2 使用collect进行归约和汇总
- 3.3 使用collect进行分组
- 3.4 使用collect来进行分区
1 认识Stream流
在Java中我们对数组和集合的操作一定是非常多的,几乎每个程序都会制造和处理数组或者集合。在Java 8之前我们处理这些数据时需要迭代、需要判断并且需要写具体的算法才能获取到想要的结果。有了流之后只需要表达想要什么,最终就会得到想要的结果,不必去担心怎么去显示的实现这些算法。
如果要去大量元素,并且想提高性能,就需要做并行处理,利用多核架构。这就需要多线程代码,但多线程的代码是很复杂的,稍有不甚就会带来很多bug。
在Java 8中引入的流就帮我们解决了这些问题,让我们的代码更加的优雅。
1.1 什么是流
Stream流是Java API的新成员,它允许以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。其实完全可以把它看成一个遍历数据集的高级迭代器。而且还可以并行的去处理数据集,无需写任何多线程代码。
下面我们用一个例子来演示Stream流:
1、创建一个水果类,name表示水果名称,weight表示水果重量。
public class Fruits {
/**
* 名称 eg: 香蕉、苹果、橙子
*/
private String name;
/**
* 重量 单位:克(g)
*/
private Integer weight;
public Fruits(String name, Integer weight) {
this.name = name;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Fruits{" +
"name='" + name + '\'' +
", weight=" + weight +
'}';
}
}
2、创建一个list集合存放一些水果类,可以理解这个list集合为果篮。
List<Fruits> list = new ArrayList<>();
list.add(new Fruits("苹果",132));
list.add(new Fruits("苹果",145));
list.add(new Fruits("苹果",120));
list.add(new Fruits("苹果",190));
list.add(new Fruits("香蕉",90));
list.add(new Fruits("香蕉",50));
list.add(new Fruits("香蕉",100));
list.add(new Fruits("西瓜",2000));
3、Java 8之前做如下操作需要这样写代码
List<Fruits> list = getList();
// 1.累加果篮中所有水果的重量
int totalWeight = 0;
for (Fruits fruits : list) {
totalWeight += fruits.getWeight();
}
System.out.println(totalWeight);
// 2.根据重量对水果排序,使用匿名类
Collections.sort(list, new Comparator<Fruits>() {
@Override
public int compare(Fruits o1, Fruits o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
});
System.out.println(list);
// 3.获取排序后的水果名称
List<String> fruitsName = new ArrayList<>();
for (Fruits fruits : list) {
fruitsName.add(fruits.getName());
}
System.out.println(fruitsName);
4、Java 8中我们做如下操作可以这样写代码
List<Fruits> list = getList();
List<String> fruitsName = list.stream()
.filter(s -> s.getWeight() > 120) // 获取重量大于120g的水果
.sorted(Comparator.comparing(Fruits::getWeight)) // 按重量从小到大排序
.map(Fruits::getName) // 获取水果名称
.collect(Collectors.toList());// 生成新的列表
// [苹果, 火龙果, 芒果, 西瓜]
System.out.println(fruitsName);
5、如果我们想使用多核架构并发来执行这段代码,只需要将stream改为parallelStream
List<Fruits> list = getList();
List<String> fruitsName = list.parallelStream()
.filter(s -> s.getWeight() > 120) // 获取重量大于120g的水果
.sorted(Comparator.comparing(Fruits::getWeight)) // 按重量从小到大排序
.map(Fruits::getName) // 获取水果名称
.collect(Collectors.toList());// 生成新的列表
// [苹果, 火龙果, 芒果, 西瓜]
System.out.println(fruitsName);
总结:
从上面的代码我们发现Stream流让代码变得更简单。其中代码是以声明性的方式写的,只需要说明想要完成什么即可,不需要去写如何实现的代码。而且还可以将几个基础操作链接起来,类似流水线一样的去处理数据。
1.2 流与集合
集合是一个内存中的数据结构,它包含数据结构中目前所有的值。流则是概念上固定的数据结构(不能添加或删除元素),其元素是按需获取的,使用一个获取一个。
1.2.1 流只能遍历一次
流和迭代器非常的类似,只能遍历一次。遍历完之后,这个流就已经被消费掉了。要先使用的话只能怪从原始数据源那里再获取一个新的流。
List<Fruits> list = getList();
Stream<Fruits> stream = list.stream();
stream.forEach(fruits -> System.out.println(fruits)); // 打印流中元素
// 再次打印则会报错
// java.lang.IllegalStateException: stream has already been operated upon or closed
stream.forEach(s -> System.out.println(s));
1.2.2 外部迭代和内部迭代
对集合使用for,for-each迭代都属于外部迭代,我们需要手动来完成迭代逻辑。而流的迭代属于内部迭代,不需要我们来实现流的迭代逻辑,只管完成逻辑代码就好,流已经替我们把迭代做了。
List<Fruits> list = getList();
// 外部迭代
for (Fruits fruits : list) {
System.out.println(fruits.getName());
}
// 内部迭代
list.stream()
.forEach(fruits -> System.out.println(fruits.getName()));
1.3 流操作
在java.util.stream.Stream中的Stream接口中定义了许多操作。我们将这些操作分为两类:中间操作和终端操作。
1.3.1 中间操作
中间操作都会返回一个符合条件的流,可以紧接着再执行中间操作或终端操作。例如上面我们在falter操作后紧接着执行sorted操作。
操作 | 类型 | 返回类型 | 操作参数 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream | Predicate | T -> boolean |
map | 中间 | Stream | Function<T, R> | T -> R |
limit | 中间 | Stream | ||
sorted | 中间 | Stream | Comparator | (T, T) -> int |
distinct | 中间 | Stream |
1.3.2 终端操作
终端操作会生成流的结果,具体的结果根据我们声明的方法来决定。例如我们上面我们通过collect来生成一个List列表。也可以使用forEach来直接打印结果。终端操作后就结束了,不会生成新的流所以也不能再接后续的操作。
操作 | 类型 | 目的 |
---|---|---|
forEach | 终端 | 消费流中的每个元素并对其应用Lambda。返回void。 |
count | 终端 | 返回流中元素个数。这一操作返回long。 |
collect | 终端 | 把流归约成一个集合,比如List、Map甚至是Integer。 |
1.3.3 使用流
流的使用一般包括三件事:
- 一个数据源(如集合)来执行一个查询
- 一个中间操作链,形成一条流的流水线
- 一个终端操作,执行流水线,并能生成结果
2 学会使用Stream流
2.1 筛选和切片
2.1.1 用谓词筛选
Streams接口支持filter方法,此方法接受一个谓词作为参数,返回一个符合谓词的元素的流。
// 筛选重量大于120g的水果
List<Fruits> list = getList();
list.stream()
.filter(s -> s.getWeight() > 120)
.forEach(System.out::println);
2.1.2 筛选各异的元素
使用distinct方法对流中的元素去重(根据元素的hashCode和equals方法)。
// 对这个集合中的元素去重
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.distinct()
.forEach(System.out::println);
2.1.3 截短流
使用limit(n)方法,可以返回一个不超过给定长度的流。
// 筛选重量大于120g的水果,选取前三个
List<Fruits> list = getList();
list.stream()
.filter(s -> s.getWeight() > 120)
.limit(3)
.forEach(System.out::println);
2.1.4 跳过元素
skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。
// 筛选重量大于120g的水果,跳过前三个,返回剩下的
List<Fruits> list = getList();
list.stream()
.filter(s -> s.getWeight() > 120)
.skip(3)
.forEach(System.out::println);
2.2 映射
一个非常常见的数据处理套路就是从某些对象中选择信息。比如在SQL里,你可以从表中选择一列。Stream API也通过map和flatMap方法提供了类似的工具。
2.2.1 map方法
map方法接受一个Function<T, R>类型的函数作为参数,流中的元素T经过这个流会映射为一个新元素R。(这里的映射是转换为新元素,是创建一个新元素而不是去修改原有的元素)。
// 获取流中每个元素的长度并打印
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
words.stream()
.map(String::length)
.forEach(System.out::println);
2.2.2 flatMap方法
我们拿一个例子来演示map方法和flatMap方法的不同:我们对集合[“Java 8”, “Lambdas”, “In”, “Action”]进行处理,返回这个列表中涉及到的所有字母,不可以重复。
正确的结果应为:[ ‘J’ , ‘a’ , ‘v’ , ’ ’ , ‘8’ , ‘L’ , ‘m’ , ‘b’ , ‘d’ , ‘s’ , ‘I’ , ‘n’ , ‘A’ , ‘c’ , ‘t’ , ‘i’ , ‘o’]
第一反应我们肯定使用如下代码来解决:
words.stream()
.map(s -> s.split(""))
.distinct()
.forEach(System.out::println);
返回结果:
这样的结果是因为我们给map输入的为Stream<String>流,split方法返回的是一个数组,所以返回的类型为Stream<String[]>流,打印数组当然就是这样的结果了。
我们可以将Stream<String[]>流使用方法Arrays.toString将数组转换为字符串,然后打印来观察结果:
words.stream()
.map(s -> s.split(""))
.distinct()
.map(Arrays::toString)
.forEach(System.out::println);
返回结果:
我们观察到这完全不是想要的结果。下面用一张图来解释为什么是这样的结果。
我们发现上面的distinct的去重其实是对String[]进行去重的。
下面我们使用flatMap(Arrays::stream)来将Stream<String[]>流转换为Stream<String>来实现想要的结果
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<String> list = words.stream()
.map(s -> s.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
System.out.println(list);
返回结果:
这正是我们想要的结果,我们也用一张图来解释一下这个的原因:
一言以蔽之,flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流.
2.3 查找和匹配
2.3.1 检查谓词是否至少匹配一个元素
使用anyMatch方法可以判断流中是否存在一个这样的元素。
// 判断流中是否存在苹果
List<Fruits> list = getList();
boolean b = list.stream().anyMatch(s -> "苹果".equals(s.getName()));
System.out.println(b);
2.3.2 检查谓词是否匹配所有元素
allMatch方法判断流中的所有元素是否都匹配,noneMatch方法判断流中所有的元素都不匹配。这两个方法都用到了“短路”,及发现一个不符合则不再继续进行判断了。
// 判断流中元素重量是否都大于10g
List<Fruits> list = getList();
boolean b = list.stream().allMatch(s -> s.getWeight() > 10);
System.out.println(b);
// 判断流中元素重量是否都不大于(小于)10g
List<Fruits> list = getList();
boolean b = list.stream().noneMatch(s -> s.getWeight() > 10);
System.out.println(b);
2.3.3 查找元素
使用findAny方法可以查找流中符合条件的元素,返回符合条件的任意一个元素。此方法也利用了“短路"操作,找到一个元素即立即结束。
// 找到重量大于100g的任意一元素
List<Fruits> list = getList();
Optional<Fruits> any = list.stream().filter(s -> s.getWeight() > 100).findAny();
System.out.println(any.get());
2.3.4 查找第一个元素
使用findFirst方法可以返回符合条件的第一个元素,findAny是任意一个元素,fingFirst是返回第一个元素,这里需要的流为顺序流(如List或者其他排好序的流)。
// 找到重量大于100g的第一个元素
List<Fruits> list = getList();
Optional<Fruits> any = list.stream().filter(s -> s.getWeight() > 100).findFirst();
System.out.println(any.get());
2.4 归约
到目前为止,你见到过的终端操作都是返回一个boolean(allMatch之类的)、void(forEach)或Optional对象(findAny等)。你也见过了使用collect来将流中的所有元素组合成一个List。
归约就是对我们得到的结果进行统计,例如得到所有水果的重量和,得到重量最高的那个水果等等。
2.4.1 元素求和
使用reduce方法可以对流中所有元素求和
// 对所有元素的重量求和
List<Fruits> list = getList();
Integer reduce = list.stream().map(Fruits::getWeight).reduce(0, Integer::sum);
System.out.println(reduce);
上面reduce方法第一个参数是默认值,可以不填,不填的话会返回一个Optional对象,考虑流中没有任何元素的情况下reduce方法不能求和,所以将结果包含在Optional对象中。
// 对所有元素的重量求和
List<Fruits> list = getList();
Optional<Integer> reduce = list.stream().map(Fruits::getWeight).reduce( Integer::sum);
System.out.println(reduce.get());
2.4.2 最大值和最小值
可以使用reduce来计算流中的最大值或者最小值
// 得到所有元素中最大的重量的值
List<Fruits> list = getList();
Optional<Integer> reduce = list.stream().map(Fruits::getWeight).reduce( Integer::max);
System.out.println(reduce.get());
// 得到所有元素中最小的重量的值
List<Fruits> list = getList();
Optional<Integer> reduce = list.stream().map(Fruits::getWeight).reduce( Integer::min);
System.out.println(reduce.get());
2.5 数值流
Java 8引入了三个原始类型特化流接口来解决这个问题:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。每个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。
2.5.1 映射到数值流
IntStream中含有特定方法执行归约操作,例如sum方法还有max、min、average方法等。sum方法默认返回的是0,而max、min和average方法返回的为OptionalInt等对应的方法。
// 得到所有元素重量和
List<Fruits> list = getList();
Integer sum = list.stream().map(Fruits::getWeight).reduce(0, Integer::sum); // 这里暗含了装箱的成本
IntStream intStream = list.stream().mapToInt(Fruits::getWeight); // 转换为IntStream流
int sum1 = intStream.sum();// IntStream流含有特定的sum方法可以直接得出和
System.out.println(sum);
System.out.println(sum1);
2.5.2 转换回对象流
List<Fruits> list = getList();
Integer sum = list.stream().map(Fruits::getWeight).reduce(0, Integer::sum);
IntStream intStream = list.stream().mapToInt(Fruits::getWeight);
Stream<Integer> boxed = intStream.boxed(); // 转换会对象流
数值范围
IntStream和LongStream中含有2个方法range和rangeClosed来帮助生成范围内的数字。他们的参数都是第一个为起始值,第二个为结束值,但range是不包含结束值的,rangeClosed是包含结束值的。
int sum = IntStream.range(0, 100).filter(s -> s % 2 == 0).sum();
System.out.println(sum);
2.6 构建流
2.6.1 由值创建流
使用静态方法Stream.of,通过显式值创建一个流。
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
可以使用Stream.empty()来创建一个空流。
2.6.2 由数组创建流
可以使用Arrays。stream(数组)将一个数组转换为流。
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
System.out.println(sum);
2.6.3 由文件生成流
Java中用于处理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。java.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法是Files.lines,它会返回一个由指定文件中的各行构成的字符串流。
long uniqueWords = 0;
try(Stream<String> lines =
Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
}
catch(IOException e){
}
2.6.4 由函数生成流:创建无限流
Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由iterate和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。
1、迭代
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
iterate方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator类型)。这里,我们使用Lambda n -> n + 2,返回的是前一个元素加上2。
2、生成
与iterate方法类似,generate方法也可让你按需生成一个无限流。但generate不是依次对每个新生成的值应用函数的。它接受一个Supplier类型的Lambda提供新的值。
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
请注意,因为你处理的是一个无限流,所以必须使用limit操作来显式限制它的大小;否则,终端操作将永远计算下去。
3 利用Stream流来处理更为复杂的数据处理查询
前面我们已经知道可以使用collect(Collectors.toList())来讲流中的元素结合成一个List。其实collect和reduce一样都是一个归约操作。可以接受一个参数将流中的元素汇总为一个结果。
reduce是接受一个BinaryOperator作为谓词,collect是接受一个Collector接口作为谓词。
3.1 collect可以做什么
还是利用我们之前的果篮List,我们可以:
- 按不同的水果进行分类,并获
- 取该水果的总重量。 将水果分为两类,重的和轻的并返回他们的总重量。
- 可以进行多级分组,先按不同水果分类,再将同一种水果分为重得和轻的。
重新定义果篮集合
List<Fruits> list = new ArrayList<>();
list.add(new Fruits("苹果",132));
list.add(new Fruits("火龙果",145));
list.add(new Fruits("火龙果",120));
list.add(new Fruits("苹果",190));
list.add(new Fruits("火龙果",90));
list.add(new Fruits("苹果",50));
list.add(new Fruits("火龙果",100));
list.add(new Fruits("火龙果",200));
将果篮中的不同水果区分出来
// java 8之前区分
Map<String,List<Fruits>> map = new HashMap<>();
for (Fruits fruits : list) {
if (map.containsKey(fruits.getName())) {
List<Fruits> fruitsList = map.get(fruits.getName());
fruitsList.add(fruits);
map.put(fruits.getName(),fruitsList);
} else {
List<Fruits> fruitsList = new ArrayList<>();
fruitsList.add(fruits);
map.put(fruits.getName(), fruitsList);
}
}
// java 8中使用collect区分
Map<String, List<Fruits>> collectMap = list.stream()
.collect(Collectors.groupingBy(Fruits::getName));
从上面的代码中我们看到了使用collect来做区分的便利性,我们只需要表达出想要什么,完全不需要去考虑该如何做。
3.2 使用collect进行归约和汇总
Collectors接口为我们已经定义了许多有用的默认方法,我们可以直接使用。
1、统计果篮(List)中水果的数量
// 使用collect进行统计
Long collect = list.stream().collect(Collectors.counting());
// 可以直接使用count来统计
long count = list.stream().count();
2、查询果篮中最重和最轻的水果
需要利用Collectors.maxBy方法,参数为Comparator比较器。
// 获取最重的水果
List<Fruits> list = getList();
Comparator<Fruits> fruitsComparator = Comparator.comparingInt(Fruits::getWeight);
Optional<Fruits> maxFruits = list.stream().collect(Collectors.maxBy(fruitsComparator));
System.out.println(maxFruits.get());
// 获取最轻的水果
List<Fruits> list = getList();
Comparator<Fruits> fruitsComparator = Comparator.comparingInt(Fruits::getWeight);
Optional<Fruits> maxFruits = list.stream().collect(Collectors.minBy(fruitsComparator));
System.out.println(maxFruits.get());
3、求果篮中水果的全部重量,平均重量,最大值,最小值,数量信息
Collectors类提供了工厂方法Collectors.summingInt来求和,summingInt将一个对象映射为求和所需的int函数,传递给collect方法执行来获取最终结果。
// 如果流为空则返回0,默认值为0
Integer collect = list.stream().collect(Collectors.summingInt(Fruits::getWeight));
System.out.println(collect);
类似的方法还有Collectors.summingLong和Collectors.summingDouble,他们作用都是一样的,用于字段为long和double的情况。
Collectors类还提供了一个更为强大的方法summarizingInt,他可以帮我求出这个流的总和、平均值、最大值和最小值,会将结果存放在作IntSummaryStatistics的类里,获取我们想要的结果即可。
IntSummaryStatistics fruitsTotal = list.stream().collect(Collectors.summarizingInt(Fruits::getWeight));
System.out.println(fruitsTotal.getMax());
System.out.println(fruitsTotal.getSum());
System.out.println(fruitsTotal.getCount());
System.out.println(fruitsTotal.getAverage());
System.out.println(fruitsTotal.getMin());
都有对应Long和Double的方法。
4、连接字符串
joining工厂方法返回的收集器会把对流中每一个对象应用toString方法得到的所有字符串连接成一个字符串。
String collect = list.stream().map(Fruits::getName).collect(Collectors.joining());
System.out.println(collect);
该字符串的可读性并不好。joining工厂方法有一个重载版本可以接受元素之间的分界符
String collect = list.stream().map(Fruits::getName).collect(Collectors.joining(","));
System.out.println(collect);
上面我们见到的收集器,其实都可以使用Collectors.reducing方法来完成,之前写的方法都是为了方便。
// 对果篮中水果重量求和,第一个参数为初始值,第二参数为转换函数,第三个参数BinaryOperator将两个元素累加
Integer collect = list.stream().collect(Collectors.reducing(0, Fruits::getWeight, (i, j) -> i + j));
System.out.println(collect);
// 找到重的水果
Optional<Fruits> collect = list.stream().collect(Collectors.reducing((i, j) -> i.getWeight() > j.getWeight() ? i : j));
System.out.println(collect.get());
3.3 使用collect进行分组
分组就是将不同类型的物品分别放到不同的组,例如在果篮(List)中可以将苹果分为一个组,火龙果分为另一个组。
// 将不同名称的元素分为不同的组
Map<String, List<Fruits>> collect = list.stream().collect(Collectors.groupingBy(Fruits::getName));
System.out.println(collect);
上面我们根据元素的名称来分组,相同名称的分为同一个组,使用Collectors.groupingBy可以帮我们完成这样的操作,最终返回的是Map<String, List>类型。
也可以根据重量来分组,小于70g的分为一组,大于等于70小于150g的分为一组,大于等于150g的分为一组。
// 将不同重量的元素分为不同的组
Map<String, List<Fruits>> collect = list.stream().collect(Collectors.groupingBy(fruits -> {
if (fruits.getWeight() < 50) {
return "小于50g";
} else if (fruits.getWeight() >= 50 && fruits.getWeight() < 150) {
return "大于等于50g小于150g";
} else {
return "大于等于150g";
}
}));
System.out.println(collect);
多级分组
多级分组就是可以先按名称分组再按重量分组,可以利用Collectors.groupingBy的另一个版本,他可以接受2个参数,第二个参数传递另一个分组方式来进行多级分组。
Map<String, Map<String, List<Fruits>>> collect = list.stream().collect(Collectors.groupingBy(Fruits::getName, Collectors.groupingBy(fruits -> {
if (fruits.getWeight() < 50) {
return "小于50g";
} else if (fruits.getWeight() >= 50 && fruits.getWeight() < 150) {
return "大于等于50g小于150g";
} else {
return "大于等于150g";
}
})));
System.out.println(collect);
第二个参数其实可以不是Collectors.groupingBy方法的,也可以是Collectors.counting方法来收集数量。
Map<String, Long> collect = list.stream().collect(Collectors.groupingBy(Fruits::getName, Collectors.counting()));
System.out.println(collect);
还可以过去不同元素中最大的元素是哪个
Map<String, Optional<Fruits>> collect = list.stream().collect(Collectors.groupingBy(Fruits::getName, Collectors.maxBy(Comparator.comparingInt(Fruits::getWeight))));
System.out.println(collect);
我们发现元素都是使用Optional类包起来的,这样防止没有元素也不会产生异常的问题。
因为分组操作的Map结果中的每个值上包装的Optional没什么用,所以你可能想要把它们去掉。要做到这一点,或者更一般地来说,把收集器返回的结果转换为另一种类型,你可以使用Collectors.collectingAndThen工厂方法返回的收集器。
Map<String, Fruits> collect = list.stream()
.collect(Collectors.groupingBy(Fruits::getName,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparingInt(Fruits::getWeight)),
Optional::get)));
System.out.println(collect);
3.4 使用collect来进行分区
分区就是分为2个区,一个true一个false,例如按重量分,小于100g的为一个区,其他的为另一个区。
Map<Boolean, List<Fruits>> collect = list.stream().collect(Collectors.partitioningBy(fruits -> fruits.getWeight() < 100));
System.out.println(collect);
上面返回的集合key分别为true和false。
将分区和分组结合起来使用,先将果篮分为小于100g的一个区和其他区,再按名字分组。
Map<Boolean, Map<String, List<Fruits>>> collect = list.stream().collect(Collectors.partitioningBy(fruits -> fruits.getWeight() < 100, Collectors.groupingBy(Fruits::getName)));
System.out.println(collect);