- 零、Stream操作的基本过程,可以归结为3个部分:
- 聚合操作reduce
- 收集结果collect
- 将结果收集到map中
- 一 collect操作 (实操部分)
- https://blog.csdn.net/lzb348110175/article/details/103923237)">0 讲解:(https://blog.csdn.net/lzb348110175/article/details/103923237)
- https://blog.csdn.net/sl1992/article/details/98900343)【这个很好】">0 collectors工具类讲解(https://blog.csdn.net/sl1992/article/details/98900343)【这个很好】
- 1收集到集合 toList、 toSet、 toCollection
- 2 收集到数组 toArray
- 3 Stream流中数据聚合/分组/分区/拼接操作
- 4 分区—partitioningBy
- 5 拼接 joining
- 6 mapping 映射
- 7 maxBy
- 8 minBy (同maxby)
- 二、sorted操作
- 三、distinct 去重
- 四 实际开发的汇总
零、Stream操作的基本过程,可以归结为3个部分:
创建一个Stream。
在一个或者多个操作中,将指定的Stream转换为另一个Stream的中间操作。
通过终止(terminal)方法来产生一个结果。该操作会强制它之前的延时操作立即执行,这之后该Stream就不能再被使用了。
中间操作都是filter()、distinct()、sorted()、map()、flatMap()等,其一般是对数据集的整理(过滤、排序、匹配、抽取等)。
终止方法往往是完成对数据集中数据的处理,如forEach(),还有allMatch()、anyMatch()、findAny()、 findFirst(),数值计算类的方法有sum、max、min、average等等。终止方法也可以是对集合的处理,如reduce()、 collect()等等。reduce()方法的处理方式一般是每次都产生新的数据集,而collect()方法是在原数据集的基础上进行更新,过程中不产生新的数据集。
List nums = Arrays.asList(1, 3, null, 8, 7, 8, 13, 10);
nums.stream().filter(num -> num != null).distinct().forEach(System.out::println);
上面代码实现为过滤null值并去重,遍历结果,实现简洁明了。使用传统方法就相对繁琐的多。另外其中 forEach即为终止操作方法,如果无该方法上面代码就没有任何操作。filter、map、forEach、findAny等方法的使用都比较简单,这里省略。
下面介绍强大的聚合操作,其主要分为两种:
可变聚合:把输入的元素们累积到一个可变的容器中,比如Collection或者StringBuilder;
其他聚合:除去可变聚合,剩下的,一般都不是通过反复修改某个可变对象,而是通过把前一次的汇聚结果当成下一次的入参,反复如此。比如reduce,count,allMatch;
聚合操作reduce
Stream.reduce,返回单个的结果值,并且reduce操作每处理一个元素总是创建一个新值。常用的方法有average, sum, min, max, count,使用reduce方法都可实现。这里主要介绍reduce方法:T reduce(T identity, BinaryOperator accumulator)
//输入两个返回一个
identity:它允许用户提供一个循环计算的初始值。
accumulator:计算的累加器,其方法签名为apply(T t,U u),在该reduce方法中第一个参数t为上次函数计算的返回值,第二个参数u为Stream中的元素,这个函数把这两个值计算apply,得到的和会被赋值给下次执行这个方法的第一个参数。有点绕看代码:
int value = Stream.of(1, 2, 3, 4).reduce(100, (sum, item) -> sum + item);
/*
解读: 第一个参数是100;即:初始值=100
第二个参数是一个BinaryOperator lamada表达式;作用是数据减少的规则(本例子中减少规则是讲当前元素于之前的数据做想加操作,并返回一个int直)
BinaryOperator 表达式的第一个参数是上次计算的返回值,如果是第一个元素的话,这个值就是reduce的第一个值(初始值);将初始值与第一个元素
做了加法操作时候,讲新的结果返回并赋值给下一个元素计算的sum;
*/
Assert.assertSame(value, 110);
/ 或者使用方法引用 /
value = Stream.of(1, 2, 3, 4).reduce(100, Integer::sum);
这个例子中100即为计算初始值,每次相加计算值都会传递到下一次计算的第一个参数。
reduce还有其它两个重载方法:Optional reduce(BinaryOperatoraccumulator)
:与上面定义基本一样,无计算初始值,所以他返回的是一个Optional。U reduce(U identity, BiFunction accumulator, BinaryOperator combiner)
:与前面两个参数的reduce方法几乎一致,你只要注意到BinaryOperator其实实现了BiFunction和BinaryOperator两个接口。(第三个参数是为了在并行运行时解决合并问题的)
收集结果collect
当你处理完流时,通常只是想查看一下结果,而不是将他们聚合为一个值。先看collect的基础方法,它接受三个参数:R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner)
supplier:一个能创造目标类型实例的方法。
accumulator:一个将当元素添加到目标中的方法。
combiner:一个将中间状态的多个结果整合到一起的方法(并发的时候会用到)。
接着看代码:
Stream stream = Stream.of(1, 2, 3, 4).filter(p -> p > 2);
List result = stream.collect(() -> new ArrayList<>(), (list, item) -> list.add(item), (one, two) -> one.addAll(two));
/ 或者使用方法引用 /
result = stream.collect(ArrayList::new, List::add, List::addAll);
这个例子即为过滤大于2的元素,将剩余结果收集到一个新的list中。
第一个方法生成一个新的ArrayList;
第二个方法中第一个参数是前面生成的ArrayList对象,第二个参数是stream中包含的元素,方法体就是把stream中的元素加入ArrayList对象中。第二个方法被反复调用直到原stream的元素被消费完毕;
第三个方法也是接受两个参数,这两个都是ArrayList类型的,方法体就是把第二个ArrayList全部加入到第一个中;
代码有点繁琐,或者使用collect的另一个重载方法:R collect(Collector collector)
注意到Collector其实是上面supplier、accumulator、combiner的聚合体。那么上面代码就变成:
List list = Stream.of(1, 2, 3, 4).filter(p -> p > 2).collect(Collectors.toList());
将结果收集到map中
先定义如下Person对象
class Person{
public String name;
public int age;
Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return String.format(“Person{name=’%s’, age=%d}”, name, age);
}
}
假设你有一个Stream对象,希望将其中元素收集到一个map中,这样就可以根据他的名称来查找对应年龄,例如:
Map result = people.collect(HashMap::new,(map,p)->map.put(p.name,p.age),Map::putAll);
/使用Collectors.toMap形式/
Map result = people.collect(Collectors.toMap(p -> p.name, p -> p.age, (exsit, newv) -> newv));
其中Collectors.toMap方法的第三个参数为键值重复处理策略,如果不传入第三个参数,当有相同的键时,会抛出一个IlleageStateException。
或者你想将Person分解为Map存储:
List
分组和分片
对具有相同特性的值进行分组是一个很常见的任务,Collectors提供了一个groupingBy方法,方法签名为:
Collector
classifier:一个获取Stream元素中主键方法。downstream:一个操作对应分组后的结果的方法。
假如要根据年龄来分组:
Map
假如我想要根据年龄分组,年龄对应的键值List存储的为Person的姓名,怎么做呢:
Map
mapping即为对各组进行投影操作,和Stream的map方法基本一致。
假如要根据姓名分组,获取每个姓名下人的年龄总和(好像需求有些坑爹):
Map sumAgeByName = people.collect(Collectors.groupingBy(p -> p.name, Collectors.reducing(0, (Person p) -> p.age, Integer::sum)));
/ 或者使用summingInt方法 /
sumAgeByName = people.collect(Collectors.groupingBy(p -> p.name, Collectors.summingInt((Person p) -> p.age)));
可以看到Java8的分组功能相当强大,当然你还可以完成更复杂的功能。另外Collectors中还存在一个类似groupingBy的方法:partitioningBy,它们的区别是partitioningBy为键值为Boolean类型的groupingBy,这种情况下它比groupingBy更有效率。
join和统计功能
话说Java8中新增了一个StringJoiner,Collectors的join功能和它基本一样。用于将流中字符串拼接并收集起来,使用很简单:
String names = people.map(p->p.name).collect(Collectors.joining(“,”))
Collectors分别提供了求平均值averaging、总数couting、最小值minBy、最大值maxBy、求和suming等操作。但是假如你希望将流中结果聚合为一个总和、平均值、最大值、最小值,那么Collectors.summarizing(Int/Long/Double)就是为你准备的,它可以一次行获取前面的所有结果,其返回值为(Int/Long/Double)SummaryStatistics。
DoubleSummaryStatistics dss = people.collect(Collectors.summarizingDouble((Person p)->p.age));
double average=dss.getAverage();
double max=dss.getMax();
double min=dss.getMin();
double sum=dss.getSum();
double count=dss.getCount();
一 collect操作 (实操部分)
理论部分另开篇章(
3.1 stream收集操作collect原理
)
0 讲解:(https://blog.csdn.net/lzb348110175/article/details/103923237)
0 collectors工具类讲解(https://blog.csdn.net/sl1992/article/details/98900343)【这个很好】
/**
* TODO Student实体类
*
* @author liuzebiao
* @Date 2020-1-10 13:38
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
class Student {
private String name;
private int age;
private int score;
}
1收集到集合 toList、 toSet、 toCollection
1 原理 【重要,值得一看】
toCollection 与toList toSet的区别就是需要一个参数,这个参数是一个Supplier类型的函数式接口,在Stream.collect()方法中的作用就是提供一个容器;详细看一下toList toSet的话,他们对于这个参数给了默认值了,(Supplier<List<T>>) ArrayList::new, (Supplier<Set<T>>) HashSet::new; 作用就是toList 返回的容器是一个ArrayList,toSet返回的容器是一个HashSet;如**果我们想要别的类型集合的话,collectors工具类没有用现成的,但是我们可以用collectors的toCollection方法实现,传入的参数就是一个产生容器的Supplier表达式**;<br />例如:<br />TreeSet<Color> collect1 = list.stream().collect(Collectors._toCollection_(**() -> new TreeSet<>(Comparator.**_**comparing**_**(s -> s.getNum()))**))**; //收集到TreeSet**<br />TreeSet<Color> collect12 = list.stream().collect(Collectors._toCollection_(**() -> new TreeSet<>()**))**; //收集到TreeSet中(注意:如果这样的写话,得优先保证Color对象能够对比,即实现**Comparable接口,否则无法比较,就会报错cannot be cast to java.lang.Comparable**)**
cannot be cast to java.lang.Comparable,遇到这个异常错误,就是说treeMap(或者treeSet)并不知道该如何put,就会报这个异常错误。第一次put时,因为为空,所以不需要比较,即不会报错。但是当第二次put时,treeMap (或者treeSet) 为了确保他是有序的就必须比较,这个时候发现这两个key根本无法比较,则抛出该异常错误。 要解决这个异常错误有两种解决办法。第一种在构造TreeMap时指定一个比较器,这个比较器用于比较两个值,并且返回一个整数值作为他们的比较结果。第二种就是key实现Comparable接口
这里toCollection()方法参数就是获取空目标容器的supplier表达式;
(上述两个表达式的区别就是,一个是TreeSet的无参构造,按照自然排序,一个是传入了Comparator,按照自定义的比较规则排序(https://www.cnblogs.com/yimian/p/6518245.html))
源码:
3.1 stream收集操作collect原理
//toCollection
public static <T, C extends Collection<T>>
Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
return new CollectorImpl<>(collectionFactory, Collection<T>::add,
(r1, r2) -> { r1.addAll(r2); return r1; },
CH_ID);
}
//toList
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
//toSet
public static <T>
Collector<T, ?, Set<T>> toSet() {
return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,
(left, right) -> { left.addAll(right); return left; },
CH_UNORDERED_ID);
}
2 示例
//1.收集数据到list集合中
stream.collect(Collectors.toList())
//2.收集数据到set集合中
stream.collect(Collectors.toSet())
//3.收集数据到指定的集合中
Collectors.toCollection(Supplier<C> collectionFactory)
stream.collect(Collectors.joining())
/**
* 收集Stream流中的数据到集合中
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
*/
public class CollectDataToCollection{
public static void main(String[] args) {
//Stream 流
Stream<String> stream = Stream.of("aaa", "bbb", "ccc", "bbb");
//收集流中的数据到集合中
//1.收集流中的数据到 list
List<String> list = stream.collect(Collectors.toList());
System.out.println(list);
//2.收集流中的数据到 set
Set<String> collect = stream.collect(Collectors.toSet());
System.out.println(collect);
//3.收集流中的数据(ArrayList)(不收集到list,set等集合中,而是)收集到指定的集合中
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
System.out.println(arrayList);
//4.收集流中的数据到 HashSet
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
System.out.println(hashSet);
//5.收集流中数据到 TreeSet
TreeSet<Color> collect1 = list.stream().collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(s -> s.getNum())))); //收集到TreeSet
TreeSet<Color> collect12 = list.stream().collect(Collectors.toCollection(() -> new TreeSet<>())); //收集到TreeSet中
}
}
2 收集到数组 toArray
//1.使用无参,收集到数组,返回值为 Object[](Object类型将不好操作)
Object[] toArray();
//2.使用有参,可以指定将数据收集到指定类型数组,方便后续对数组的操作
<A> A[] toArray(IntFunction<A[]> generator);
/**
* 收集Stream流中的数据到数组中
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
*/
public class CollectDataToArray{
public static void main(String[] args) {
//Stream 流
Stream<String> stream = Stream.of("aaa", "bbb", "ccc", "bbb");
//2.1 使用 toArray()无参
Object[] objects = stream.toArray();
for (Object o: objects) {//此处无法使用.length() 等方法
System.out.println("data:"+o);
}
//2.2 使用有参返回指定类型数组
//无参不好的一点就是返回的是 Object[] 类型,操作比较麻烦.想要拿到长度,Object是拿不到长度的
String[] strings = stream.toArray(String[]::new);
for(String str : strings){
System.out.println("data:"+str + ",length:"+str.length());
}
}
}
3 Stream流中数据聚合/分组/分区/拼接操作
1 聚合—平均值
averagingDouble方法返回一个Collector收集器,它生成应用于输入元素的double值函数的算术平均值。如果没有元素,则结果为0。
@Test
public void testAveragingDouble() {
Double averagingDouble = menu.stream().collect(Collectors.averagingDouble(Student::getTotalScore));
Optional.ofNullable(averagingDouble).ifPresent(System.out::println);
}
// 557.7142857142857
2 聚合—collectingAndThen
collectingAndThen方法调整Collector收集器以执行其它的结束转换。例如,可以调整toList()收集器,以始终生成一个不可变的列表:
List<Student> studentList = menu.stream().collect(
Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
System.out.println(studentList);
示例1:以指定字符串The Average totalScore is->输出所有学生的平均总成绩
@Test
public void testCollectingAndThen() {
Optional.ofNullable(menu.stream().collect(
Collectors.collectingAndThen(
Collectors.averagingInt(Student::getTotalScore), a -> "The Average totalScore is->" + a)
)).ifPresent(System.out::println);
}
// The Average totalScore is->557.7142857142857
示例2:将流中数据收集到treeSet中,然后将结果转换成ArrayList
List<Color> resList = list.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(s -> s.getNum()))), ArrayList::new));
3 聚合—counting
counting方法返回一个Collector收集器接受T类型的元素,用于计算输入元素的数量。如果没有元素,则结果为0
@Test
public void testCounting() {
Optional.of(menu.stream().collect(Collectors.counting())).ifPresent(System.out::println);
}
// 7
示例
/**
* Stream流数据--聚合操作
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
* @author liuzebiao
* @Date 2020-1-10 13:37
*/
public class CollectDataToArray{
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 58, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 77)
);
//求总和(使用Stream流的map()和reduce()方法亦可求和)
//map()和reduce()方法实现
//Integer reduce = studentStream.map(s -> s.getAge()).reduce(0, Integer::sum);
//(聚合)简化前
//Integer ageSum = studentStream.collect(Collectors.summingInt(s->s.getAge()));
//(聚合)使用方法引用简化
Integer ageSum = studentStream.collect(Collectors.summingInt(Student::getAge));
System.out.println("年龄总和:"+ageSum);
//求平均值
//(聚合)简化前
//Double avgScore = studentStream.collect(Collectors.averagingInt(s->s.getScore()));
//(聚合)使用方法引用简化
Double avgScore = studentStream.collect(Collectors.averagingInt(Student::getScore));
System.out.println("分数平均值:"+avgScore);
//统计数量(Stream流 count()方法亦可)
//count()方法实现
//long count = studentStream.count();
//(聚合)统计数量
Long count = studentStream.collect(Collectors.counting());
System.out.println("数量为:"+count);
}
}
4 分组 groupingBy toMap
groupingBy(Function) groupingBy(Function, Collector) groupingBy(Function, Supplier, Collector),这三个方法关系是一次递进的,都是返回一个Map
4.1groupingBy(Function)
groupingBy(Function)方法返回一个Collector收集器对T类型的输入元素执行”group by”操作,根据分类函数对元素进行分组,并将结果返回到Map。
分类函数将元素映射到某些键类型K。收集器生成一个Map
>,其键是将分类函数应用于输入元素得到的值,其对应值为List,其中包含映射到分类函数下关联键的输入元素。 无法保证返回的Map或List对象的类型,可变性,可序列化或线程安全性。 注意: 返回的Collector收集器不是并发的。对于并行流管道,combiner函数通过将键从一个映射合并到另一个映射来操作,这可能是一个昂贵的操作。如果不需要保留元素出现在生成的Map收集器中的顺序,则使用groupingByConcurrent(Function)可以提供更好的并行性能。
@Test
public void testGroupingByFunction() {
Map<Student.GradeType, List<Student>> collect = menu.stream()
.collect(Collectors.groupingBy(Student::getGradeType));
Optional.ofNullable(collect).ifPresent(System.out::println);
}
// {TWO=[Student{name='李四', totalScore=531, local=true, gradeType=TWO}], THREE=[Student{name='刘一', totalScore=721, local=true, gradeType=THREE}, Student{name='陈二', totalScore=637, local=true, gradeType=THREE}, Student{name='张三', totalScore=666, local=true, gradeType=THREE}, Student{name='王五', totalScore=483, local=false, gradeType=THREE}, Student{name='赵六', totalScore=367, local=true, gradeType=THREE}], ONE=[Student{name='孙七', totalScore=499, local=false, gradeType=ONE}]}
4.2groupingBy(Function, Collector)
groupingBy(Function, Collector)方法返回一个Collector收集器,对T类型的输入元素执行级联”group by”操作,根据分类函数对元素进行分组,然后使用指定的下游Collector收集器对与给定键关联的值执行缩减操作。
分类函数将元素映射到某些键类型K。下游收集器对T类型的元素进行操作,并生成D类型的结果。产生收集器生成Map
。 返回的Map的类型,可变性,可序列化或线程安全性无法保证。 注意: 返回的Collector收集器不是并发的。对于并行流管道,combiner函数通过将键从一个映射合并到另一个映射来操作,这可能是一个昂贵的操作。如果不需要保留向下游收集器提供元素的顺序,则使用groupingByConcurrent(Function, Collector)可以提供更好的并行性能。
@Test
public void testGroupingByFunctionAndCollector() {
Optional.of(menu.stream()
.collect(Collectors.groupingBy(Student::getGradeType, Collectors.counting())))
.ifPresent(System.out::println);
}
// {THREE=5, ONE=1, TWO=1}
4.3 groupingBy(Function, Supplier, Collector)
groupingBy(Function, Supplier, Collector)方法返回一个Collector收集器,对T类型的输入元素执行级联”group by”操作,根据分类函数对元素进行分组,然后使用指定的下游Collector收集器对与给定键关联的值执行缩减操作。收集器生成的Map是使用提供的工厂函数创建的()
分类函数将元素映射到某些键类型K。下游收集器对T类型的元素进行操作,并生成D类型的结果。产生收集器生成Map
。 注意: 返回的Collector收集器不是并发的。对于并行流管道,combiner函数通过将键从一个映射合并到另一个映射来操作,这可能是一个昂贵的操作。如果不需要保留向下游收集器提供元素的顺序,则使用groupingByConcurrent(Function, Supplier, Collector)可以提供更好的并行性能。
@Test
public void testGroupingByFunctionAndSupplierAndCollector() {
Map<Student.GradeType, Double> map = menu.stream()
.collect(Collectors.groupingBy(
Student::getGradeType,
TreeMap::new,
Collectors.averagingInt(Student::getTotalScore)));
Optional.of(map.getClass()).ifPresent(System.out::println);
Optional.of(map).ifPresent(System.out::println);
}
// class java.util.TreeMap
// {ONE=499.0, TWO=531.0, THREE=574.8}
//接收一个 Function 参数
groupingBy(Function<? super T, ? extends K> classifier)
/**
* Stream流数据--分组操作
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
* @author liuzebiao
* @Date 2020-1-10 13:37
*/
public class CollectDataToArray{
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 56),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 99),
new Student("柳岩", 52, 53)
);
//1.按照具体年龄分组
Map<Integer, List<Student>> map = studentStream.collect(Collectors.groupingBy((s -> s.getAge())));
map.forEach((key,value)->{
System.out.println(key + "---->"+value);
});
//2.按照分数>=60 分为"及格"一组 <60 分为"不及格"一组
Map<String, List<Student>> map = studentStream.collect(Collectors.groupingBy(s -> {
if (s.getScore() >= 60) {
return "及格";
} else {
return "不及格";
}
}));
map.forEach((key,value)->{
System.out.println(key + "---->"+value);
});
//collectors.toMap(参数1,参数2,参数三) 这个是将一个结合转换成map,map的key就是用参数1的funcation函数生成的;map的value就是用参数2的function函数生成的,参数三的作用就是如果key冲突的话,用参数三(BiFunction函数)将两个结果合并;(我们的示例是,如果冲突了就用后者代替前者;)
Map<Long, SetsOrgLevelSimpleInfoDto> dtoMap = orgs.stream().collect(Collectors.toMap(SetsOrgLevelSimpleInfoDto::getBranchId, o -> o, (o1, o2) -> o2));
}
}
52---->[Student{name='赵丽颖', age=52, score=56}, Student{name='柳岩', age=52, score=53}]
56---->[Student{name='杨颖', age=56, score=88}, Student{name='迪丽热巴', age=56, score=99}]
-----------------------------------------------------------------------------------------------
不及格---->[Student{name='赵丽颖', age=52, score=56}, Student{name='柳岩', age=52, score=53}]
及格---->[Student{name='杨颖', age=56, score=88}, Student{name='迪丽热巴', age=56, score=99}]
3 多级分组操作
//接收两个参数: 1.Function 参数 2.Collector多级分组
groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream)
/**
* Stream流数据--多级分组操作
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
* @author liuzebiao
* @Date 2020-1-10 13:37
*/
public class CollectDataToArray{
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33)
);
//多级分组
//1.先根据年龄分组,然后再根据成绩分组
//分析:第一个Collectors.groupingBy() 使用的是(年龄+成绩)两个维度分组,所以使用两个参数 groupingBy()方法
// 第二个Collectors.groupingBy() 就是用成绩分组,使用一个参数 groupingBy() 方法
Map<Integer, Map<Integer, Map<String, List<Student>>>> map = studentStream.collect(Collectors.groupingBy(str -> str.getAge(), Collectors.groupingBy(str -> str.getScore(), Collectors.groupingBy((student) -> {
if (student.getScore() >= 60) {
return "及格";
} else {
return "不及格";
}
}))));
map.forEach((key,value)->{
System.out.println("年龄:" + key);
value.forEach((k2,v2)->{
System.out.println("\t" + v2);
});
});
}
}
年龄:52
{不及格=[Student{name='柳岩', age=52, score=33}]}
{及格=[Student{name='赵丽颖', age=52, score=95}]}
年龄:56
{不及格=[Student{name='迪丽热巴', age=56, score=55}]}
{及格=[Student{name='杨颖', age=56, score=88}]}
4 分区—partitioningBy
我们在前面学习了 Stream流中数据的分组操作,我们可以根据属性完成对数据的分组。接下来我们介绍分区操作,我们通过使用 Collectors.partitioningBy() ,根据返回值是否为 true,把集合分为两个列表,一个 true 列表,一个 false 列表。
分组和分区的区别就在:分组可以有多个组。分区只会有两个区( true 和 false)
//1.一个参数
partitioningBy(Predicate<? super T> predicate)
//2.两个参数(多级分区)
partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)
/**
* Stream流数据--多级分组操作
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
* @author liuzebiao
* @Date 2020-1-10 13:37
*/
public class CollectDataToArray{
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33)
);
//分区操作
Map<Boolean, List<Student>> partitionMap = studentStream.collect(Collectors.partitioningBy(s -> s.getScore() > 60));
partitionMap.forEach((key,value)->{
System.out.println(key + "---->" + value);
});
}
}
false---->[Student{name='迪丽热巴', age=56, score=55}, Student{name='柳岩', age=52, score=33}]
true---->[Student{name='赵丽颖', age=52, score=95}, Student{name='杨颖', age=56, score=88}]
5 拼接 joining
//无参数—等价于 joining(“”);
joining()
//一个参数,连接符是什么
joining(CharSequence delimiter)
//三个参数(前缀+后缀),连接符+前缀+后缀
joining(CharSequence delimiter, CharSequence prefix,CharSequence suffix)
/**
* Stream流数据--多级分组操作
* 备注:切记Stream流只能被消费一次,流就失效了
* 如下只是示例代码
* @author liuzebiao
* @Date 2020-1-10 13:37
*/
public class CollectDataToArray{
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("赵丽颖", 52, 95),
new Student("杨颖", 56, 88),
new Student("迪丽热巴", 56, 55),
new Student("柳岩", 52, 33)
);
//拼接操作
//无参:join()
String joinStr1 = studentStream.map(s -> s.getName()).collect(Collectors.joining());
System.out.println(joinStr1);
//一个参数:joining(CharSequence delimiter)
String joinStr2 = studentStream.map(s -> s.getName()).collect(Collectors.joining(","));
System.out.println(joinStr2);
//三个参数:joining(CharSequence delimiter, CharSequence prefix,CharSequence suffix)
String joinStr3 = studentStream.map(s -> s.getName()).collect(Collectors.joining("—","^_^",">_<"));
System.out.println(joinStr3);
}
}
赵丽颖杨颖迪丽热巴柳岩
赵丽颖,杨颖,迪丽热巴,柳岩
^_^赵丽颖—杨颖—迪丽热巴—柳岩>_<
6 mapping 映射
mapping(Function<? super T,? extends U> mapper, Collector<? super U,A,R> downstream)
mapping方法通过在累积之前将映射函数应用于每个输入元素,将Collector收集器接受U类型的元素调整为一个接受T类型的元素。
即:映射:先对集合中的元素进行映射,然后再对映射的结果使用Collectors操作
@Test
public void testMapping() {
Optional.of(menu.stream().collect(Collectors.mapping(Student::getName, Collectors.joining(",")))) //先对流中每个元素执行funaction映射,然后对结果再执行collector操作(这里的操作时拼接,也可以干别的)
.ifPresent(System.out::println);
}
// 刘一,陈二,张三,李四,王五,赵六,孙七
@Test //这里是先转成map然后再拼接的方式;
public void testJoiningWithDelimiterAndPrefixAndSuffix() {
Optional.of(menu.stream().map(Student::getName).collect(Collectors.joining(",", "Names[", "]")))
.ifPresent(System.out::println);
}
// Names[刘一,陈二,张三,李四,王五,赵六,孙七]
7 maxBy
maxBy方法返回一个Collector收集器,它根据给定的Comparator比较器生成最大元素,描述为Optional
//聚合操作
//获取最大值(Stream流 max()方法亦可)
//max()方法实现
//Optional<Student> max = studentStream.max((s1, s2) -> s1.getScore() - s2.getScore());
//(聚合)实现
Optional<Student> max = studentStream.collect(Collectors.maxBy((s1, s2) -> s1.getScore() - s2.getScore()));
System.out.println("最大值:"+max.get());
@Test
public void testMaxBy() {
menu.stream().collect(Collectors.maxBy(Comparator.comparingInt(Student::getTotalScore))) //这里使用了collectors工具类返回了comparator;在往上的例子就是自己手写了comparator的lamada表达式;
.ifPresent(System.out::println);
}
// Student{name='刘一', totalScore=721, local=true, gradeType=THREE}
8 minBy (同maxby)
minBy方法返回一个Collector收集器,它根据给定的Comparator比较器生成最小元素,描述为Optional
//获取最小值(Stream流 min()方法亦可)
//min()方法实现
//Optional<Student> min = studentStream.max((s1, s2) -> s2.getScore() - s1.getScore());
//(聚合)实现
Optional<Student> min = studentStream.collect(Collectors.minBy((s1, s2) -> s1.getScore() - s2.getScore()));
System.out.println("最小值:"+min.get());
@Test
public void testMinBy() {
menu.stream().collect(Collectors.minBy(Comparator.comparingInt(Student::getTotalScore)))
.ifPresent(System.out::println);
}
// Student{name='赵六', totalScore=367, local=true, gradeType=THREE}
二、sorted操作
1 排序
推荐;
1. Comparator.comparing(类::属性一).reversed();
2. Comparator.comparing(类::属性一,Comparator.reverseOrder());
两种排序是完全不一样的,一定要区分开来 1 是得到排序结果后再排序,2是直接进行排序,很多人会混淆导致理解出错,2更好理解,建议使用2
List<类> list; 代表某集合
//返回 对象集合以类属性一升序排序
list.stream().sorted(Comparator.comparing(类::属性一));
//返回 对象集合以类属性一降序排序 注意两种写法
list.stream().sorted(Comparator.comparing(类::属性一).reversed());//先以属性一升序,结果进行属性一降序
list.stream().sorted(Comparator.comparing(类::属性一,Comparator.reverseOrder()));//以属性一降序
//返回 对象集合以类属性一升序 属性二升序
list.stream().sorted(Comparator.comparing(类::属性一).thenComparing(类::属性二));
//返回 对象集合以类属性一降序 属性二升序 注意两种写法
list.stream().sorted(Comparator.comparing(类::属性一).reversed().thenComparing(类::属性二));//先以属性一升序,升序结果进行属性一降序,再进行属性二升序
list.stream().sorted(Comparator.comparing(类::属性一,Comparator.reverseOrder()).thenComparing(类::属性二));//先以属性一降序,再进行属性二升序
//返回 对象集合以类属性一降序 属性二降序 注意两种写法
list.stream().sorted(Comparator.comparing(类::属性一).reversed().thenComparing(类::属性二,Comparator.reverseOrder()));//先以属性一升序,升序结果进行属性一降序,再进行属性二降序
list.stream().sorted(Comparator.comparing(类::属性一,Comparator.reverseOrder()).thenComparing(类::属性二,Comparator.reverseOrder()));//先以属性一降序,再进行属性二降序
//返回 对象集合以类属性一升序 属性二降序 注意两种写法
list.stream().sorted(Comparator.comparing(类::属性一).reversed().thenComparing(类::属性二).reversed());//先以属性一升序,升序结果进行属性一降序,再进行属性二升序,结果进行属性一降序属性二降序
list.stream().sorted(Comparator.comparing(类::属性一).thenComparing(类::属性二,Comparator.reverseOrder()));//先以属性一升序,再进行属性二降序
//空/Null数据排序
list.stream().sorted(Comparator.comparing(类::属性一).thenComparing(item -> item.属性二, Comparator.nullsLast(Date::compareTo))).collect(Collectors.toList());
//空/Null数据分组
Map<String, List<类>> map = list.stream().collect(Collectors.groupingBy(item -> {
if (item.属性一 == null || item.属性一.equals("")) {
return "";
}
return DateFormat.getDateInstance().format(item.属性一);
}))
1. Comparator.comparing(类::属性一).reversed();
2. Comparator.comparing(类::属性一,Comparator.reverseOrder());
两种排序是完全不一样的,一定要区分开来 1 是得到排序结果后再排序,2是直接进行排序,很多人会混淆导致理解出错,2更好理解,建议使用2
三、distinct 去重
推荐使用方式三;
List res = list.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(s -> s.getNum()))), ArrayList::new));//按照对象的Num去重
package com.binc.testspring.common.lamada;
import com.binc.testspring.common.lamada.pojo.Color;
import org.junit.Test;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class StreamDistinctTest {
public static void main(String[] args) {
List<Color> list= new ArrayList<>();
list.add(new Color(5, "DogA5"));
list.add(new Color(5, "DogA5_2"));
list.add(new Color(4, "DogA4"));
list.add(new Color(6, "DogA6"));
List<Color> collect = list.stream().distinct().collect(Collectors.toList());
System.out.println(collect);
// 按整个个对象去重
System.out.println("整个对象去重:");
list.stream().distinct()
.forEach(System.out::println);
// 指定某个字段去重
System.out.println("指定age属性去重(方法一):");
list.stream().filter(distinctByKey1(s -> s.getNum()))
.forEach(System.out::println);
// 方法二(用循环)
System.out.println("指定age属性去重(方法二):");
TreeSet<Color> colors = new TreeSet<>(Comparator.comparing(s -> s.getNum())); // 这是利用了treeset的不可重复特性,定制了比较方法,如果num属性一致就默认他是相同对象;
for (Color color : list) {
colors.add(color);
}
new ArrayList<>(colors)
.forEach(System.out::println);
//推荐用这个方式
// 方法三:(是方法二的变形),此方法来源于:https://blog.csdn.net/qq_28988969/article/details/81119587
// collectingAndThen的作用是先将list流中的数据收集到treeSet中(利用treeset的去重特性),然后将treeset的结果再转成List集合;
System.out.println("指定age属性去重(方法三):");
list.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(s -> s.getNum()))), ArrayList::new))
.forEach(System.out::println);
//这里是将流中数据收集到treeset中
System.out.println("指定age属性去重(方法三):");
TreeSet<Color> collect1 = list.stream().collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(s -> s.getNum()))));
TreeSet<Color> collect12 = list.stream().collect(Collectors.toCollection(() -> new TreeSet<>()));
System.out.println(collect12);
System.out.println(collect1);
}
// 次方法来源于:https://blog.csdn.net/haiyoung/article/details/80934467
static <T> Predicate<T> distinctByKey1(Function<? super T, ?> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
}
四 实际开发的汇总
1 stream求和 lamada求和queryVo.getUseTypeList().stream().reduce(Integer::_sum_).orElse(null)
2 stream排序 lamada排序
3 stream 排序
5 stream 差集
6 对象某个属性链接成字符串
7 倒叙 注意去除null这个影响
1 stream求和 lamada求和
BigDecimal reduce = list.stream().filter(m -> Objects.nonNull(m.getAccountBalance())).map(FundAccountDto::getAccountBalance).reduce(BigDecimal.ZERO, BigDecimal::add);
2 stream排序 lamada排序
list.stream().sorted(Comparator.comparing(EcontWorkFlowDto::getCreateTm).reversed()).findFirst();
按照EcontWorkFlowDto::getCreateTm排升序 降序 第一个
3 stream 排序
list.stream()
.filter(p -> p.getBizId() != null)
.sorted(Comparator.comparingLong(EcontTimeAxisDto::getId).reversed())
.findFirst();
4 stream 求和
BigDecimal accountBalance = notCore_sorted.stream().map(FundAccountSubEntity::getAccountBalance).reduce(BigDecimal.ZERO, BigDecimal::add);
5 stream 差集
List<FundAccountEntity> diff = accounts.stream()
.filter(account ->
!subs.stream()
.map(sub ->sub.getAccountId()).collect(Collectors.toList())
.contains(account.getId()))
.collect(Collectors.toList());
6 // 这里将集合中的某个属性连接成串儿;
log.info("差集结果:"+diff.stream().map( d -> ""+d.getId()).collect(Collectors.joining(",")));
7 倒叙 注意去除null这个影响
List<FundAccountDto> collect = list.stream().filter(m -> Objects.nonNull(m.getId())).sorted(Comparator.comparingLong(FundAccountDto::getId).reversed()).collect(Collectors.toList());
// 交集
List<String> intersection = list1.stream().filter(item -> list2.contains(item)).collect(Collectors.toList());
// 差集 (list1 - list2)
List<String> reduce1 = list1.stream().filter(item -> !list2.contains(item)).collect(Collectors.toList());
// 差集 (list2 - list1)
List<String> reduce2 = list2.stream().filter(item -> !list1.contains(item)).collect(Collectors.toList());
// 并集
List<String> listAll = list1.parallelStream().collect(Collectors.toList());
List<String> listAll2 = list2.parallelStream().collect(Collectors.toList());
listAll.addAll(listAll2);
// 去重并集