Java8的新特性--Stream API

写在前面

Java8中有两大最为重要的改变:

  • Lambda表达式
  • Stream API(java.util.stream.*)

Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤、映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行数据库查询。也可以使用Stream API来并行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。

Stream是什么

流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

即流这个数据渠道,在数据传输过程中,对数据源做一系列流水线式的中间操作,然后产生一个新的流,这个流不会改变原来的流

集合讲的是数据,流讲的是计算

注意

  1. Stream 自己不会存储元素。
  2. Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  3. Stream 操作是延迟执行的。这意味着他们会等需要结果的时候才执行。

Stream操作的三个步骤

1. 创建Stream

1.1 通过Collection集合

通过Collection系列集合提供的Stream()串行流或parallelStream()并行流。

其中,Java8中的Collection接口扩展了,提供了两个获取流的方法:

  • 返回一个顺序流
    /*
    * @since 1.8
    */
    default Stream stream() {
        return StreamSupport.stream(spliterator(), false);
    }
  • 返回一个并行流
    /*
     * @since 1.8
     */
    default Stream parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }

下面我们写个通过Collection系列集合流创建流的例子。

    @Test
    public void test01(){
        //通过Collection系列集合提供的Stream()或parallelStream()
        List list = new ArrayList<>();
        Stream stream = list.stream();
        Stream stringStream = list.parallelStream();
    }

1.2 通过数组Arrays

        //通过数组Arrays 中的静态方法stream()获取数组
        Employee[] employees = new Employee[10];
        Stream stream2 = Arrays.stream(employees);

通过Stream流中的of()方法来创建

        //通过Stream类中的静态方法of()
        Stream stream3 = Stream.of("aa", "bb", "cc", "dd");

1.3 创建无限流

创建无限流的两种方法:迭代、生成
我们先来看一下迭代Stream.iterate()方法的定义


迭代方法的定义

我们再来看一下生成的方式, Stream.generate()这里需要一个供给型的参数


生成

再来写下创建无限流的例子

        //创建无限流
        //迭代   Stream.iterate()传俩参数,第一个是种子即起始值,第二个参数是Lambda表达式即对起始值进行的操作,
        //这里是生成偶数
        Stream stream4 = Stream.iterate(0, (x) -> x + 2);
        //无限流就是没有限制,需要一个终止操作限制
        stream4.limit(4).forEach(System.out::println);
        //生成的方式
        Stream generate5 = Stream.generate(() -> Math.random());
        generate5.limit(5).forEach(System.out::println);

运行结果

0
2
4
6
0.3204608858702849
0.495403965457605
0.9188968488509007
0.18726624455121932
0.3791774193868236

2. 中间操作

一个中间操作链,对数据源的数据进行处理。

多个中间操作可以连接起来形成一个流水线,除非流水线上触发了终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性处理,称为“惰性求值”或者“延迟加载”。

2.1 筛选与切片

方法 描述
filter(Predicate p) 接收Lambda,从流中排除某些元素
distinct() 筛选,通过流所生成元素的hashCode()和equal()去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补

下面我们一个个来分析:

2.1.1 filter(Predicate p)

接收Lambda,从流中排除某些元素

    @Test
    public void test02(){
        List employees = Arrays.asList(
                new Employee("张三",68,9000),
                new Employee("李四",38,8000),
                new Employee("王五",50,4000),
                new Employee("赵六",18,3000),
                new Employee("田七",8,1000));
        //中间操作,不会执行任何操作
        //filter(Predicate p) 接收Lambda,从流中排除某些元素
        Stream employeeStream = employees.stream().filter((e) -> {
            System.out.println("StreamAPI的中间操作");
            return e.getAge() > 35;
        });

        //终止操作  当只有中间操作时执行是没有结果的,因为只有执行终止操作以后所有的中间操作才一次性全部处理,即惰性求值
        employeeStream.forEach(System.out::println);

    }

运行结果

StreamAPI的中间操作
Employee{name='张三', age=68, salary=9000.0}
StreamAPI的中间操作
Employee{name='李四', age=38, salary=8000.0}
StreamAPI的中间操作
Employee{name='王五', age=50, salary=4000.0}
StreamAPI的中间操作
StreamAPI的中间操作

从运行结果可以看出,迭代操作不是我们做的,是由Stream API 帮我们完成的,再也不需要我们自己完成这个迭代操作了,这也叫内部迭代。与内部迭代相对应的是外部迭代,也就是我们自己写的迭代。

    //外部迭代,我们自己写的迭代
    @Test
    public void test03(){
        Iterator iterator = employees.iterator();
        if(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
2.1.2 limit(long maxSize)

limit(),下面我们过滤公司中薪资大于5000的雇员之后,获取其中前2个

    //limit()过滤公司中薪资大于5000的雇员之后,获取其中前2个雇员的信息
    @Test
    public void test04(){
         employees.stream()
                .filter((e) -> {
                    System.out.println("===短路===");
                    return e.getSalary()>5000;
                })
                .limit(2)
                .forEach(System.out::println);
    }

运行结果


运行结果

从运行结果我们看出,迭代操作只执行了两次,也就是说只要找到满足条件的结果之后,就不再进行迭代了,这个过程就叫“短路”。所以说它也可以提高效率,跟我们之前学的短路&&和||有点类似。

2.1.3 skip(long n)

扔掉也即跳过前几个元素

    List employees = Arrays.asList(
            new Employee("张三",68,9000),
            new Employee("李四",38,8000),
            new Employee("王五",50,4000),
            new Employee("赵六",18,3000),
            new Employee("田七",8,1000));
    //skip()过滤公司中薪资大于5000的雇员之后,跳过前2个雇员的信息
    @Test
    public void test05(){
        employees.stream()
                .filter((e) -> e.getSalary()>2000)
                .skip(2)
                .forEach(System.out::println);
    }

运行结果,跳过了8000和9000的工资

Employee{name='王五', age=50, salary=4000.0}
Employee{name='赵六', age=18, salary=3000.0}
2.1.4 distinct()

我们首先给employees集合中添加几个重复的元素赵六

    @Test
    public void test06(){
        List employees1 = Arrays.asList(
                new Employee("张三",68,9000),
                new Employee("李四",38,8000),
                new Employee("王五",50,4000),
                new Employee("赵六",18,3000),
                new Employee("赵六",18,3000),
                new Employee("赵六",18,3000),
                new Employee("赵六",18,3000),
                new Employee("田七",8,1000));
        employees1.stream()
                .filter((e) -> e.getSalary()>2000)
                .distinct()
                .forEach(System.out::println);
    }

运行结果


运行结果

我们发现结果并没有去重,因为,他是通过流所生成元素的hashCode()和equals()来去除重复元素的。所以,要想去除重复元素,必须得重写Employee实体类中的hashCode()和equals()这两个方法。

重写后的Employee如下

package com.cqq.java8.lambda;

import java.util.Objects;

/**
 * @author caoqianqian
 * @Description:
 * @date 2021/3/6 9:24
 */
public class Employee {
    private String name;
    private int age;
    private double salary;

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public Employee() {
    }

    public Employee(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age &&
                Double.compare(employee.salary, salary) == 0 &&
                Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, salary);
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }
}

我们再运行一下结果,已经是去重了的。


运行结果

2.2 映射

方法 描述
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的DoubleStream
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的IntStream
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的LongStream
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把左右流连成一个流
2.2.1 map(Function f)

接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素

    @Test
    public void test07(){
        //将字母转大写
        List list = Arrays.asList("aa","bb","cc");
        list.stream()
                .map(str -> str.toUpperCase())
                .forEach(System.out::println);
        //提取员工名字
        employees.stream()
                .map((e) -> e.getName())
                .forEach(System.out::println);
    }

运行结果

AA
BB
CC
张三
李四
王五
赵六
田七
2.2.2 flatMap(Function f)

接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连成另一个流。

我们先来写一个方法来解析字符串,并把字符串中的一个一个的字符给单独提取出来,放到集合中。

    public static Stream filterCharacter(String str){
        List list  = new ArrayList<>();
        for (Character c:str.toCharArray()) {
            list.add(c);
        }

        return list.stream();
    }

再来看这个测试例子

    //faltMap
    @Test
    public void test08(){
        //提取list里的一个个字符串的每一个字符
        List list = Arrays.asList("aa","bb","cc");

        //通过map提取到的是一个stream流里还是stream流
        Stream> streamStream = list.stream()
                .map(TestStreamAPI::filterCharacter);
        //这里得嵌套遍历才可以循环出每个字符串的结果
        streamStream.forEach(
                s -> s.forEach(System.out::println)
        );

        //通过flatMap提取到的是一个stream流
        Stream streamStream2 = list.stream()
                .flatMap(TestStreamAPI::filterCharacter);
        //这样就不用了嵌套遍历了
        streamStream2.forEach(System.out::println);

    }

其实map方法就相当于Collaction的add方法,如果add的是个集合的话就会变成二维数组,而flatMap 的话就相当于Collaction的addAll方法,参数如果是集合的话,只是将2个集合合并,而不是变成二维数组。

2.3 排序

方法 描述
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator comp) 产生一个新流,其中按比较器顺序排序

下面我们编写测试例子,首先我们先创建一个员工的集合:

 List employees = Arrays.asList(
            new Employee("张三",68,9000),
            new Employee("李四",38,8000),
            new Employee("王五",50,4000),
            new Employee("赵六",18,3000),
            new Employee("田七",8,1000));

用sort() 自然排序实现一串字符list的自然排序。用sorted(Comparator comp) 比较器排序,实现员工按年龄排序,如果年龄相等按姓名排。

    //排序
    //sort() 自然排序
    //sorted(Comparator comp) 比较器排序
    @Test
    public void test09(){
        //sort() 将字母按自然醒顺序排序
        List list = Arrays.asList("bb","aa","cc");
        list.stream()
                .sorted()
                .forEach(System.out::println);

        //sorted(Comparator comp) 比较器排序 按年龄排序,如果年龄相等按姓名排
        employees.stream()
                .sorted((e1,e2) -> {
                    if(e1.getAge()== e2.getAge()){
                        return e1.getName().compareTo(e2.getName());
                    }else {
                        return Integer.compare(e1.getAge(),e2.getAge());
                    }
                })
                .forEach(System.out::println);
    }

运行结果


aa
bb
cc
Employee{name='田七', age=8, salary=1000.0}
Employee{name='赵六', age=18, salary=3000.0}
Employee{name='李四', age=38, salary=8000.0}
Employee{name='王五', age=50, salary=4000.0}
Employee{name='张三', age=68, salary=9000.0}

3. 终止操作(终端操作)

一个终止操作,执行中间操作链,并产生结果。
即终止操作会从流的流水线生成结果,其结果可以是任何不是流的值,例如List、Integer、甚至是void。

3.1 查找与匹配

方法 描述
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配所有元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素的总个数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 内部迭代(使用Collection接口需要用户去做迭代,成为外部迭代。相反,Stream API使用内部迭代--它帮你把迭代做好了)
3.1.1 allMatch(Predicate p)检查是否匹配所有元素

下面写一个例子,检查公司中所有员工是否处于空闲状态。

    @Test
    public void test10(){
        List employees1 = Arrays.asList(
                new Employee("张三",68,9000, Employee.Status.FREE),
                new Employee("李四",38,8000,Employee.Status.BUSY),
                new Employee("王五",50,4000,Employee.Status.VOCATION),
                new Employee("赵六",18,3000,Employee.Status.BUSY),
                new Employee("田七",8,1000,Employee.Status.FREE));
        boolean b = employees1.stream()
                .allMatch(e -> e.getStatus().equals(Employee.Status.FREE));
        System.out.println("===allMatch===="+b);

    }

Employee 的实体类中加个状态和对应添加这个参数的构造方法

package com.cqq.java8.lambda;

import java.util.Objects;

public class Employee {
    private String name;
    private int age;
    private double salary;
    private Status status;

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public Employee(String name, int age, double salary,Status status) {
        this.name = name;
        this.age = age;
        this.salary = salary;
        this.status = status;
    }

    public Employee() {
    }

    public Employee(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age &&
                Double.compare(employee.salary, salary) == 0 &&
                Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, salary);
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public Status getStatus() {
        return status;
    }

    public void setStatus(Status status) {
        this.status = status;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public enum Status{
        FREE,
        BUSY,
        VOCATION;
    }
}

3.1.2 anyMatch(Predicate p)检查是否至少匹配一个元素
    //anyMatch
    @Test
    public void test11(){
        boolean c = employees1.stream()
                .anyMatch(e -> e.getStatus().equals(Employee.Status.FREE));
        System.out.println("===anyMatch===="+c);
    }
3.1.3 noneMatch(Predicate p)检查是否没有匹配所有元素
    //noneMatch
    @Test
    public void test12(){
        boolean d = employees1.stream()
                .noneMatch(e -> e.getStatus().equals(Employee.Status.FREE));
        System.out.println("===anyMatch===="+d);

    }
3.1.4 findFirst()返回第一个元素
    //findFirst
    @Test
    public void test13(){
        Optional first = employees1.stream()
                .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
                .findFirst();
        System.out.println(first.get());
    }

注意:上面findFirst返回的是一个Optional对象,它将我们的Employee封装了一层,这是为了避免了空指针。而且这个对象为我们提供了orElse方法,也就是当我们拿到的这个对象为空的时候,我们可以传入一个新的对象去代替它。

3.1.5 findAny()返回当前流中任意元素
    //findAny
    @Test
    public void test14(){
        Optional first = employees1.stream()
                .sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
                .findAny();
        System.out.println(first.get());
    }
3.1.6 count()返回流中的总个数
    //count
    @Test
    public void test15(){
         long count = employees1.stream()
                .count();
        System.out.println(count);
    }
3.1.7 max()返回流中最大的元素
    //max
    @Test
    public void test16(){
        //两种方法
        Optional max1 = employees1.stream()
                .max((e1,e2) -> Integer.compare(e1.getAge(),e2.getAge()));
        Optional max2 = employees1.stream()
                .max(Comparator.comparingInt(Employee::getAge));
        System.out.println(max1);
    }
3.1.8 min()返回流中最小的元素
    //min
    @Test
    public void test17(){
        //两种方法
        Optional max1 = employees1.stream()
                .min((e1,e2) -> Integer.compare(e1.getAge(),e2.getAge()));
        Optional max2 = employees1.stream()
                .min(Comparator.comparingInt(Employee::getAge));
        System.out.println(max1);
    }
3.1.9 forEach(Consumer c)

这个我们很熟悉了,每个测试例子都循环打印输出

3.2 归约

方法 描述
reduce(T identity,Binaryoperator b) 可以将流中元素反复结合在一起,得到一个值。返回T
reduce(Binaryoperator b) 可以将流中元素反复结合在一起,得到一个值。返回Optional
3.2.1 reduce(T identity,Binaryoperator b)
    //reduce()
    @Test
    public void test18(){
        List list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        //首先,需要传一个起始值,然后,传入的是一个二元运算。
        Integer count = list.stream().reduce(0, (x, y) -> x + y);
        System.out.println(count);
    }

reduce 归约,把流中的元素按照(x, y) -> x + y的形式进行累加操作。首先0是一个起始值,然后从流中取出第一个元素1作为y进行累加即结果为1,再将这个结果1作为x,再从流中取出一个元素2作为y进行累加,结果为3,一次类推,反复结合,最终得到一个新值。

3.2.2 reduce(Binaryoperator b)
    //reduce()
    @Test
    public void test19(){
        List list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        //没有起始值,则有可能结果为空,所以返回的值会被封装到Optional中。
        Optional reduce = list.stream().reduce((x, y) -> x + y);
        //另一种写法
        Optional reduce1 = list.stream().reduce(Integer::sum);

        System.out.println(reduce);
        System.out.println(reduce1);

    }

没有起始值,则有可能结果为空,所以返回的值会被封装到Optional中。

备注:map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名。

3.3 收集

方法 描述
collect(Collect c) 将流转化为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法

Collector接口中方法的实现,决定了如何对流执行收集操作(如收集到List、Set及Map中),而且Collectors实用类中提供了许多静态方法,可以方便的创建常见的收集器实例。Collectors实用类中常用的静态方法如下表所示:

方法 返回类型 作用
toList() List 把流中的元素收集到List集合中
toSet() Set 把流中的元素收集到Set集合中
toCollection(...) Collection 把流中的元素收集到创建好的集合中
counting() Long 计算流中元素个数
averagingDouble(...) Double 计算流中元素Double类型的平均值
summingDouble(...) Double 计算流中元素Double类型的总和
maxBy(...) Optional 根据比较器选择最大值
minBy(...) Optional 根据比较器选择最小值
groupingBy(...) Map> 根据某属性的值对流分组,属性为K,结果为V
partitioningBy(...) Map> 根据True或false进行分区
summarizingBy(...) DoubleSummaryStatistics 收集流(流中元素为Double类型)的统计值。如:平均值
joining(...) String 连接流中每个字符串
3.3.1 toList() 将流转换成List
    //toList()
    @Test
    public void test20(){
        List list = employees.stream().map(Employee::getAge).collect(Collectors.toList());
        list.forEach(System.out::println);
    }
3.3.2 toSet() 将流转换成Set
    //toSet()
    @Test
    public void test21(){
        Set collect = employees.stream().map(Employee::getAge).collect(Collectors.toSet());
        collect.forEach(System.out::println);
    }
3.3.3 toCollection() 将流转换成其他类型的集合

将流转换成HashSet

    //toCollection()
    @Test
    public void test22(){
        //换成HashSet
        HashSet collect = employees.stream().map(Employee::getAge).collect(Collectors.toCollection(HashSet::new));
        collect.forEach(System.out::println);
    }
3.3.4 counting()计算流中元素个数
    //counting()
    @Test
    public void test23(){
        //总数
        Long collect = employees.stream().collect(Collectors.counting());
        System.out.println(collect);
    }
3.3.5 averagingDouble(...)计算流中元素Double类型的平均值
    //averagingDouble()
    @Test
    public void test24(){
        //平均值
        Double collect1 = employees.stream().collect(Collectors.averagingDouble(Employee::getAge));
        System.out.println(collect1);
    }
3.3.6 summingDouble(...)计算流中元素Double类型的总和

IntSummaryStatistics里包含{count=, sum=, min=, average=, max=}这几项需要哪个值,通过get获取就行

summarizingInt

    //summingDouble()
    @Test
    public void test25(){
        //平均值
        DoubleSummaryStatistics collect1 = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
        IntSummaryStatistics collect = employees.stream().collect(Collectors.summarizingInt(Employee::getAge));
        System.out.println(collect);
        System.out.println(collect.getSum());
    }

再来看下summarizingInt()

    //summarizingInt()
    @Test
    public void test26(){
        //平均值  IntSummaryStatistics里包含{count=5, sum=182, min=8, average=36.400000, max=68}这几项需要哪个获取就行
        IntSummaryStatistics collect = employees.stream().collect(Collectors.summarizingInt(Employee::getAge));
        System.out.println(collect);
        //从IntSummaryStatistics里取sum
        System.out.println(collect.getSum());
    }

执行结果

IntSummaryStatistics{count=5, sum=182, min=8, average=36.400000, max=68}
182

Collectors.summingLong()同上一样,传输传入Long型即可。

3.3.7 minBy(...)根据比较器选择最小值
    //minBy()
    @Test
    public void test28(){
        Optional min = employees.stream().collect(Collectors.minBy((x, y) -> Integer.compare(x.getAge(), y.getAge())));
        System.out.println(min.get());
    }
3.3.8 maxBy(...)根据比较器选择最小值
    //maxBy()
    @Test
    public void test27(){
        Optional max = employees.stream().collect(Collectors.maxBy((x, y) -> Integer.compare(x.getAge(), y.getAge())));
        System.out.println(max.get());
        //等价于这种形式
        Optional max1 = employees.stream().max((x, y) -> Integer.compare(x.getAge(), y.getAge()));
        System.out.println(max1.get());

    }
3.3.9 groupingBy(...)根据某属性的值对流分组,属性为K,结果为V

分组分为一级分组和多级分组,下面例子分别进行了演示:

一级分组的例子


 //groupingBy()一级分组
    @Test
    public void test29(){
        Map> collect = employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus));
        System.out.println(collect);
    }

运行结果

{
BUSY=[Employee{name='张三', age=68, salary=9000.0}, Employee{name='李四', age=38, salary=8000.0}, Employee{name='赵六', age=18, salary=3000.0}],
VOCATION=[Employee{name='王五', age=50, salary=4000.0}],
FREE=[Employee{name='田七', age=8, salary=1000.0}]
}

多级分组的例子


    //多级分组
    @Test
    public void test30(){
       Map>> map =  employees.stream()
                .collect(Collectors.groupingBy(Employee::getStatus,Collectors.groupingBy(e -> {
                    if(e.getAge()<35){
                        return "青年";
                    }else if(e.getAge()<50){
                        return "中年";
                    }else {
                        return "老年";
                    }
                })));
        System.out.println(map);
    }

运行结果

{
FREE={青年=[Employee{name='田七', age=8, salary=1000.0}]},
BUSY={青年=[Employee{name='赵六', age=18, salary=3000.0}], 老年=[Employee{name='张三', age=68, salary=9000.0}], 中年=[Employee{name='李四', age=38, salary=8000.0}]},
VOCATION={老年=[Employee{name='王五', age=50, salary=4000.0}]}
}

3.3.10 partitioningBy(...)根据True或false进行分区
    //partitioningBy()
    @Test
    public void test31(){
        Map> map = employees.stream()
                .collect(Collectors.partitioningBy(e -> e.getSalary() > 5000));
        System.out.println(map);
    }

运行结果

{
false=[Employee{name='王五', age=50, salary=4000.0}, Employee{name='赵六', age=18, salary=3000.0}, Employee{name='田七', age=8, salary=1000.0}],
true=[Employee{name='张三', age=68, salary=9000.0}, Employee{name='李四', age=38, salary=8000.0}]
}

3.3.12 joining(...)连接流中每个字符串
    //joining()
    @Test
    public void test32(){
        String collect = employees.stream().map(Employee::getName)
                .collect(Collectors.joining(","));
        System.out.println(collect);
    }

运行结果

张三,李四,王五,赵六,田七

至此,我们Stream的基本操作差不多就结束了,我们只需要多加练习,熟练使用就能提高效率啦。

你可能感兴趣的:(Java8的新特性--Stream API)