零、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,得到的和会被赋值给下次执行这个方法的第一个参数。有点绕看代码:

  1. int value = Stream.of(1, 2, 3, 4).reduce(100, (sum, item) -> sum + item);
  2. /*
  3. 解读: 第一个参数是100;即:初始值=100
  4. 第二个参数是一个BinaryOperator lamada表达式;作用是数据减少的规则(本例子中减少规则是讲当前元素于之前的数据做想加操作,并返回一个int直)
  5. BinaryOperator 表达式的第一个参数是上次计算的返回值,如果是第一个元素的话,这个值就是reduce的第一个值(初始值);将初始值与第一个元素
  6. 做了加法操作时候,讲新的结果返回并赋值给下一个元素计算的sum;
  7. */
  8. 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 personToMap = people.collect(ArrayList::new, (list, p) -> {
Mapmap = new HashMap<>();
map.put(“name”, p.name);
map.put(“age”, p.age);
list.add(map);
}, List::addAll);

分组和分片
对具有相同特性的值进行分组是一个很常见的任务,Collectors提供了一个groupingBy方法,方法签名为:
Collector groupingBy(Function classifier, Collector downstream)
classifier:一个获取Stream元素中主键方法。downstream:一个操作对应分组后的结果的方法。
假如要根据年龄来分组:
Map peopleByAge = people.filter(p -> p.age > 12).collect(Collectors.groupingBy(p -> p.age, Collectors.toList()));
假如我想要根据年龄分组,年龄对应的键值List存储的为Person的姓名,怎么做呢:
Map peopleByAge = people.collect(Collectors.groupingBy(p -> p.age, Collectors.mapping((Person p) -> p.name, Collectors.toList())));
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>,一个参数的,决定了map的key的取值,value用的就是流中的每个对象; 两个参数的key value都按照自己的方式取值;三个参数的可以改变生成的map类型,改成treeMap类型等;;
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);


    // 去重并集