Java8 Stream API 基础操作

基础操作

map/filter

先来个最简单:

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         // 我们先学中间操作        // 1.先获取流(不用管其他乱七八糟的创建方式,记住这一个就能应付95%的场景)        Stream stream = list.stream();        // 2.过滤得到年纪大于18岁的(filter表示过滤【得到】符合传入条件的元素,而不是过滤【去除】)        Stream filteredByAgeStream = stream.filter(person -> person.getAge() > 18);        // 3.只要名字,不需要整个Person对象(为什么在这个案例中,filter只能用Lambda,map却可以用方法引用?)        Stream nameStream = filteredByAgeStream.map(Person::getName);        // 4.现在返回值是Stream,没法直接使用,帮我收集成List        List nameList = nameStream.collect(Collectors.toList());    }    @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}

再来一个:

public static void main(String[] args) {     // 直接链式操作    List nameList = list.stream()            .filter(person -> person.getAge() > 18)            .map(Person::getName)            .collect(Collectors.toList());}

试着加入sorted()玩一下。

在此之前,我们先来见见一位老朋友:Comparator。

这个接口其实早在JDK1.2就有了,但当时只有两个方法:

  1. compare()
  2. equals()

JDK1.8通过默认方法的形式引入了很多额外的方法,比如reversed()、Comparing()等。

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         // JDK8之前:Collections工具类+匿名内部类。Collections类似于Arrays工具类,我经常用Arrays.asList()        Collections.sort(list, new Comparator() {             @Override            public int compare(Person p1, Person p2) {                 return p1.getName().length()-p2.getName().length();            }        });                // JDK8之前:List本身也实现了sort()        list.sort(new Comparator() {             @Override            public int compare(Person p1, Person p2) {                 return p1.getName().length()-p2.getName().length();            }        });                // JDK8之后:Lambda传参给Comparator接口,其实就是实现Comparator#compare()。注意,equals()是Object的,不妨碍        list.sort((p1,p2)->p1.getName().length()-p2.getName().length());                // JDK8之后:使用JDK1.8为Comparator接口新增的comparing()方法        list.sort(Comparator.comparingInt(p -> p.getName().length()));    }        @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}

来玩一下Stream#sorted(),看看和List#sort()有啥区别。

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         // 直接链式操作        List nameList = list.stream()                .filter(person -> person.getAge() > 18)                .map(Person::getName)                .collect(Collectors.toList());        System.out.println(nameList);        // 我想按姓名长度排序        List sortedNameList = list.stream()                .filter(person -> person.getAge() > 18)                .map(Person::getName)                .sorted()                .collect(Collectors.toList());        System.out.println(sortedNameList);        // Stream(默认自然排序)        // 按照长度倒序(注意细节啊,str2-str1才是倒序)        List realSortedNameList = list.stream()                .filter(person -> person.getAge() > 18)                .map(Person::getName)                .sorted((str1, str2) -> str2.length() - str1.length())                .collect(Collectors.toList());        System.out.println(realSortedNameList);        // sorted()有重载方法,是sorted(Comparator)        // 上面Lambda其实就是调用sorted(Comparator),用Lambda给Comparator接口赋值        // 但Comparator还供了一些方法,能返回Comparator实例        List optimizeNameList = list.stream()                .filter(person -> person.getAge() > 18)                .map(Person::getName)                .sorted(Comparator.reverseOrder())                .collect(Collectors.toList());        System.out.println(optimizeNameList);        // 又是一样的套路,Comparator.reverseOrder()返回的其实是一个Comparator        // 但上面的有点投机取巧,来个正常点的,使用Comparator.comparing()        List result1 = list.stream()                .filter(person -> person.getAge() > 18)                .map(Person::getName)                .sorted(Comparator.comparing(t -> t, (str1, str2) -> str2.length() - str1.length()))                .collect(Collectors.toList());        System.out.println(result1);        // 我去,更麻烦了!!        // 不急,我们先来了解上面案例中Comparator的两个参数        // 第一个是Function映射,就是指定要排序的字段,由于经过上一步map操作,已经是name了,就不需要映射了,所以是t->t        // 第二个是比较规则                // 我们把map和sorted调换一下顺序,看起来就不那么别扭了        List result2 = list.stream()                .filter(person -> person.getAge() > 18)                .sorted(Comparator.comparing(Person::getName, String::compareTo).reversed())                .map(Person::getName)                .collect(Collectors.toList());        System.out.println(result2);        // 为什么Comparator.comparing().reversed()可以链式调用呢?        // 因为Comparator.comparing()返回的还是Comparator对象~    }    @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}

limit/skip

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         List result = list.stream()                .filter(person -> person.getAge() > 17)                // peek()先不用管,它不会影响整个流程,就是打印看看filter操作后还剩什么元素                .peek(person -> System.out.println(person.getName()))                .skip(1)                .limit(2)                .map(Person::getName)                .collect(Collectors.toList());        System.out.println(result);    }        @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}
结果==== 过滤后的元素有3个 ====iamiron==== skip(1)+limit(2)后的元素 ====[am, iron]

所谓的skip(N)就是跳过前面N个元素,limit(N)就是只取N个元素。

collect

collect()是最重要、最难掌握、同时也是功能最丰富的方法。

最常用的4个方法:Collectors.toList()、Collectors.toSet()、Collectors.toMap()、Collectors.joining()

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         // 最常用的4个方法        // 把结果收集为List        List toList = list.stream().map(Person::getAddress).collect(Collectors.toList());        System.out.println(toList);                // 把结果收集为Set        Set toSet = list.stream().map(Person::getAddress).collect(Collectors.toSet());        System.out.println(toSet);                // 把结果收集为Map,前面的是key,后面的是value,如果你希望value是具体的某个字段,可以改为toMap(Person::getName, person -> person.getAge())        Map nameToPersonMap = list.stream().collect(Collectors.toMap(Person::getName, person -> person));        System.out.println(nameToPersonMap);        // 把结果收集起来,并用指定分隔符拼接        String result = list.stream().map(Person::getAddress).collect(Collectors.joining("~"));        System.out.println(result);    }        @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}

关于collect收集成Map的操作,有一个小坑需要注意:

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("iron", 17, "宁波", 888.8));    }    public static void main(String[] args) {         Map nameToPersonMap = list.stream().collect(Collectors.toMap(Person::getName, person -> person));        System.out.println(nameToPersonMap);    }    @Getter    @Setter    @AllArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;        @Override        public String toString() {             return "Person{" +                    "name='" + name + '\'' +                    ", age=" + age +                    ", address='" + address + '\'' +                    ", salary=" + salary +                    '}';        }    }}

尝试运行上面的代码,会观察到如下异常:

Exception in thread "main" java.lang.IllegalStateException: Duplicate key Person{ name='iron', age=21, address='杭州', salary=888.8}

这是因为toMap()不允许key重复,我们必须指定key冲突时的解决策略(比如,保留已存在的key):

public static void main(String[] args) {     Map nameToPersonMap = list.stream()            .collect(Collectors.toMap(Person::getName, person -> person, (preKey, nextKey) -> preKey));    System.out.println(nameToPersonMap);}

如果你希望key覆盖,可以把(preKey, nextKey) -> preKey)换成(preKey, nextKey) -> nextKey)。

你可能会在同事的代码中发现另一种写法:

public static void main(String[] args) {     Map nameToPersonMap = list.stream().collect(Collectors.toMap(Person::getName, Function.identity());    System.out.println(nameToPersonMap);}

Function.identity()其实就是v->v:

但它依然没有解决key冲突的问题,而且对于大部分人来说,相比person->person,Function.identity()的可读性不佳。

聚合:max/min/count

max/min

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         // 匿名内部类的方式,实现Comparator,明确按什么规则比较(所谓最大,必然是在某种规则下的最值)        Optional maxAge = list.stream().map(Person::getAge).max(new Comparator() {             @Override            public int compare(Integer age1, Integer age2) {                 return age1 - age2;            }        });        System.out.println(maxAge.orElse(0));        Optional max = list.stream().map(Person::getAge).max(Integer::compareTo);        System.out.println(max.orElse(0));    }        @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}
public static void main(String[] args) {     long count = list.stream().filter(person -> person.getAge() > 18).count();    System.out.println(count);}

去重:distinct

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         long count = list.stream().map(Person::getAddress).distinct().count();        System.out.println(count);    }        @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}

所谓“去重”,就要明确怎样才算“重复”。那么,distinct()是基于什么标准呢?

还是那两样:hashCode() 和 equals(),所以记得重写这两个方法(一般使用Lombok的话问题不大)。

distinct() 提供的去重功能比较简单,就是判断对象重复。如果希望实现更细粒度的去重,比如根据对象的某个属性去重,可以怎么做呢?可以参考:分享几种 Java8 中通过 Stream 对列表进行去重的方法

一般来说,这些用法已经覆盖实际开发90%的场景了。这里写目录标题

基础操作

map/filter

先来个最简单:

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         // 我们先学中间操作        // 1.先获取流(不用管其他乱七八糟的创建方式,记住这一个就能应付95%的场景)        Stream stream = list.stream();        // 2.过滤得到年纪大于18岁的(filter表示过滤【得到】符合传入条件的元素,而不是过滤【去除】)        Stream filteredByAgeStream = stream.filter(person -> person.getAge() > 18);        // 3.只要名字,不需要整个Person对象(为什么在这个案例中,filter只能用Lambda,map却可以用方法引用?)        Stream nameStream = filteredByAgeStream.map(Person::getName);        // 4.现在返回值是Stream,没法直接使用,帮我收集成List        List nameList = nameStream.collect(Collectors.toList());    }    @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}

再来一个:

public static void main(String[] args) {     // 直接链式操作    List nameList = list.stream()            .filter(person -> person.getAge() > 18)            .map(Person::getName)            .collect(Collectors.toList());}

试着加入sorted()玩一下。

在此之前,我们先来见见一位老朋友:Comparator。

这个接口其实早在JDK1.2就有了,但当时只有两个方法:

  1. compare()
  2. equals()

JDK1.8通过默认方法的形式引入了很多额外的方法,比如reversed()、Comparing()等。

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         // JDK8之前:Collections工具类+匿名内部类。Collections类似于Arrays工具类,我经常用Arrays.asList()        Collections.sort(list, new Comparator() {             @Override            public int compare(Person p1, Person p2) {                 return p1.getName().length()-p2.getName().length();            }        });                // JDK8之前:List本身也实现了sort()        list.sort(new Comparator() {             @Override            public int compare(Person p1, Person p2) {                 return p1.getName().length()-p2.getName().length();            }        });                // JDK8之后:Lambda传参给Comparator接口,其实就是实现Comparator#compare()。注意,equals()是Object的,不妨碍        list.sort((p1,p2)->p1.getName().length()-p2.getName().length());                // JDK8之后:使用JDK1.8为Comparator接口新增的comparing()方法        list.sort(Comparator.comparingInt(p -> p.getName().length()));    }        @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}

来玩一下Stream#sorted(),看看和List#sort()有啥区别。

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         // 直接链式操作        List nameList = list.stream()                .filter(person -> person.getAge() > 18)                .map(Person::getName)                .collect(Collectors.toList());        System.out.println(nameList);        // 我想按姓名长度排序        List sortedNameList = list.stream()                .filter(person -> person.getAge() > 18)                .map(Person::getName)                .sorted()                .collect(Collectors.toList());        System.out.println(sortedNameList);        // Stream(默认自然排序)        // 按照长度倒序(注意细节啊,str2-str1才是倒序)        List realSortedNameList = list.stream()                .filter(person -> person.getAge() > 18)                .map(Person::getName)                .sorted((str1, str2) -> str2.length() - str1.length())                .collect(Collectors.toList());        System.out.println(realSortedNameList);        // sorted()有重载方法,是sorted(Comparator)        // 上面Lambda其实就是调用sorted(Comparator),用Lambda给Comparator接口赋值        // 但Comparator还供了一些方法,能返回Comparator实例        List optimizeNameList = list.stream()                .filter(person -> person.getAge() > 18)                .map(Person::getName)                .sorted(Comparator.reverseOrder())                .collect(Collectors.toList());        System.out.println(optimizeNameList);        // 又是一样的套路,Comparator.reverseOrder()返回的其实是一个Comparator        // 但上面的有点投机取巧,来个正常点的,使用Comparator.comparing()        List result1 = list.stream()                .filter(person -> person.getAge() > 18)                .map(Person::getName)                .sorted(Comparator.comparing(t -> t, (str1, str2) -> str2.length() - str1.length()))                .collect(Collectors.toList());        System.out.println(result1);        // 我去,更麻烦了!!        // 不急,我们先来了解上面案例中Comparator的两个参数        // 第一个是Function映射,就是指定要排序的字段,由于经过上一步map操作,已经是name了,就不需要映射了,所以是t->t        // 第二个是比较规则                // 我们把map和sorted调换一下顺序,看起来就不那么别扭了        List result2 = list.stream()                .filter(person -> person.getAge() > 18)                .sorted(Comparator.comparing(Person::getName, String::compareTo).reversed())                .map(Person::getName)                .collect(Collectors.toList());        System.out.println(result2);        // 为什么Comparator.comparing().reversed()可以链式调用呢?        // 因为Comparator.comparing()返回的还是Comparator对象~    }    @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}

limit/skip

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         List result = list.stream()                .filter(person -> person.getAge() > 17)                // peek()先不用管,它不会影响整个流程,就是打印看看filter操作后还剩什么元素                .peek(person -> System.out.println(person.getName()))                .skip(1)                .limit(2)                .map(Person::getName)                .collect(Collectors.toList());        System.out.println(result);    }        @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}
结果==== 过滤后的元素有3个 ====iamiron==== skip(1)+limit(2)后的元素 ====[am, iron]

所谓的skip(N)就是跳过前面N个元素,limit(N)就是只取N个元素。

collect

collect()是最重要、最难掌握、同时也是功能最丰富的方法。

最常用的4个方法:Collectors.toList()、Collectors.toSet()、Collectors.toMap()、Collectors.joining()

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         // 最常用的4个方法        // 把结果收集为List        List toList = list.stream().map(Person::getAddress).collect(Collectors.toList());        System.out.println(toList);                // 把结果收集为Set        Set toSet = list.stream().map(Person::getAddress).collect(Collectors.toSet());        System.out.println(toSet);                // 把结果收集为Map,前面的是key,后面的是value,如果你希望value是具体的某个字段,可以改为toMap(Person::getName, person -> person.getAge())        Map nameToPersonMap = list.stream().collect(Collectors.toMap(Person::getName, person -> person));        System.out.println(nameToPersonMap);        // 把结果收集起来,并用指定分隔符拼接        String result = list.stream().map(Person::getAddress).collect(Collectors.joining("~"));        System.out.println(result);    }        @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}

关于collect收集成Map的操作,有一个小坑需要注意:

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("iron", 17, "宁波", 888.8));    }    public static void main(String[] args) {         Map nameToPersonMap = list.stream().collect(Collectors.toMap(Person::getName, person -> person));        System.out.println(nameToPersonMap);    }    @Getter    @Setter    @AllArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;        @Override        public String toString() {             return "Person{" +                    "name='" + name + '\'' +                    ", age=" + age +                    ", address='" + address + '\'' +                    ", salary=" + salary +                    '}';        }    }}

尝试运行上面的代码,会观察到如下异常:

Exception in thread "main" java.lang.IllegalStateException: Duplicate key Person{ name='iron', age=21, address='杭州', salary=888.8}

这是因为toMap()不允许key重复,我们必须指定key冲突时的解决策略(比如,保留已存在的key):

public static void main(String[] args) {     Map nameToPersonMap = list.stream()            .collect(Collectors.toMap(Person::getName, person -> person, (preKey, nextKey) -> preKey));    System.out.println(nameToPersonMap);}

如果你希望key覆盖,可以把(preKey, nextKey) -> preKey)换成(preKey, nextKey) -> nextKey)。

你可能会在同事的代码中发现另一种写法:

public static void main(String[] args) {     Map nameToPersonMap = list.stream().collect(Collectors.toMap(Person::getName, Function.identity());    System.out.println(nameToPersonMap);}

Function.identity()其实就是v->v:

但它依然没有解决key冲突的问题,而且对于大部分人来说,相比person->person,Function.identity()的可读性不佳。

聚合:max/min/count

max/min

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         // 匿名内部类的方式,实现Comparator,明确按什么规则比较(所谓最大,必然是在某种规则下的最值)        Optional maxAge = list.stream().map(Person::getAge).max(new Comparator() {             @Override            public int compare(Integer age1, Integer age2) {                 return age1 - age2;            }        });        System.out.println(maxAge.orElse(0));        Optional max = list.stream().map(Person::getAge).max(Integer::compareTo);        System.out.println(max.orElse(0));    }        @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}
public static void main(String[] args) {     long count = list.stream().filter(person -> person.getAge() > 18).count();    System.out.println(count);}

去重:distinct

public class StreamTest {     private static List list;    static {         list = new ArrayList<>();        list.add(new Person("i", 18, "杭州", 999.9));        list.add(new Person("am", 19, "温州", 777.7));        list.add(new Person("iron", 21, "杭州", 888.8));        list.add(new Person("man", 17, "宁波", 888.8));    }    public static void main(String[] args) {         long count = list.stream().map(Person::getAddress).distinct().count();        System.out.println(count);    }        @Data    @AllArgsConstructor    @NoArgsConstructor    static class Person {         private String name;        private Integer age;        private String address;        private Double salary;    }}

所谓“去重”,就要明确怎样才算“重复”。那么,distinct()是基于什么标准呢?

还是那两样:hashCode() 和 equals(),所以记得重写这两个方法(一般使用Lombok的话问题不大)。

distinct() 提供的去重功能比较简单,就是判断对象重复。如果希望实现更细粒度的去重,比如根据对象的某个属性去重,可以怎么做呢?

一般来说,这些用法已经覆盖实际开发90%的场景了。

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章