常用的Stream流操作 done!

919 阅读16分钟

时不时被大佬们追问,为啥不用Stream流...😶(一脸无奈)

泛型的学习

Stream流的学习离不开泛型,泛型的学习文章推荐:

Java泛型深入理解

java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一

Java中 ? extends T 和 ? super T 的理解

Java泛型中的PECS原则

话外:感动哭了,本来想汇总一下常用的Stream流操作,结果被泛型活活折磨一整下午。好东西要学会分享,链接已奉上😯😯😯 PECS原则

自问自答环节: 引入泛型之前 -> 抛出问题:①获取一个值必须强制类型转换;②插入值时,无法约束预期的类型(以ArrayList举例,底层维护Object[])-> 引出泛型,本质:数据类型参数化 -> 工作原理:编译器编译时,先进行类型检查,然后进行类型擦除,并且在类型参数出现的插入强制转换的指令实现(IDEA自带Java类的字节码查看工具:点击java类/View/ShowBatecode) -> 泛型的使用:泛型类、泛型接口和泛型方法 -> 为什么引入泛型通配符(Generic<Integer>不能为Generic<Number>类传参) -> 泛型通配符上下界(PECE)

(突然想到一句话:每个技术的出现,都是为了解决一个难题)


Stream 流的基操

Stream将要处理的元素集合看作一种流,在流的过程中,借助 Stream API 对流中的元素进行操作,比如:筛选、排序、聚合等。

Stream可以由数组或集合创建,流的操作分为中间操作和终端操作。stream 流有不存储数据,不改变数据源,和延迟执行的特性。

萌新类,用于测试:

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
class Person {
    private String name;
    private int salary;
    private int age;
    private String sex;
    private String area;
}

1. 遍历/匹配(foreach/find/match)

Optional类是一个可以为 null 的容器对象。如果值存在则isPresent()方法会返回 true,调用get()方法会返回该对象。

Stream 也是支持类似集合的遍历和匹配元素的,只是 Stream 中的元素是以 Optional 类型存在的。Stream 的遍历、匹配非常简单。

@Test
    public void streamStudyTest1() {
        List<Integer> integerList = Lists.newArrayList(7, 6, 9, 3, 8, 2, 1);
        // 遍历输出符合条件的元素
        integerList.stream().filter(num -> num > 6).forEach(System.out::print);
        // 匹配第一个
        Optional<Integer> firstInteger = integerList.stream().filter(num -> num > 8).findFirst();
        if (firstInteger.isPresent()) {
            System.out.printf("\n%d", firstInteger.get());
        }
        // 匹配任意(适用于并行流,顺序流无意义)
        Optional<Integer> anyInteger = integerList.parallelStream().filter(num -> num > 3).findAny();
        if (anyInteger.isPresent()) {
            System.out.printf("\n%d", anyInteger.get());
        }
    }

输出:

798
9
6

2. 筛选(filter)

筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。

① 筛选工资高于8000的萌新,并形成新的集合:

@Test
    public void streamStudyTest2() {
        List<Person> personList = Lists.newArrayList(
                new Person("Tom", 8900, 23, "male", "New York"),
                new Person("Jack", 7000, 25, "male", "Washington"),
                new Person("Lily", 7800, 21, "female", "Washington"),
                new Person("Anni", 8200, 24, "female", "New York"),
                new Person("Owen", 9500, 25, "male", "New York"),
                new Person("Alisa", 7900, 26, "female", "New York")
        );
        List<String> result = personList.stream().filter(person -> person.getSalary() > 8000)
                .map(Person::getName).collect(Collectors.toList());
        System.out.println(result);
    }

输出萌新:

[Tom, Anni, Owen]

3. 聚合(max/min/count)

maxmincount这些字眼你一定不陌生,没错,在mysql中我们常用它们进行数据统计。Java stream 中也引入了这些概念和用法,极大地方便了我们对集合、数组的数据统计工作。

① 获取 String 集合中最长的元素

 @Test
    public void streamStudyTest3() {
        List<String> stringList = Lists.newArrayList("adnm", "admmt", "pot", "xbangd", "weoujgsd");
        Optional<String> maxLengthString = stringList.stream().max(Comparator.comparingInt(String::length));
        System.out.println(maxLengthString.get());
    }

输出:

weoujgsd

② 获取 Integer 集合中的最大值

@Test
    public void streamStudyTest4() {
        List<Integer> integerList = Lists.newArrayList(7, 6, 9, 3, 8, 2, 1);
        // 自然排序
        Optional<Integer> maxInteger = integerList.stream().max(Integer::compareTo);
        System.out.println(maxInteger.get());
        // 自定义排序
        Optional<Integer> maxInteger2 = integerList.stream().max(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
        System.out.println(maxInteger2.get());
    }

输出:

9
1

③ 获取工资最高的萌新

@Test
    public void streamStudyTest5() {
        List<Person> personList = Lists.newArrayList(
                new Person("Tom", 8900, 23, "male", "New York"),
                new Person("Jack", 7000, 25, "male", "Washington"),
                new Person("Lily", 7800, 21, "female", "Washington"),
                new Person("Anni", 8200, 24, "female", "New York"),
                new Person("Owen", 9500, 25, "male", "New York"),
                new Person("Alisa", 7900, 26, "female", "New York")
        );
        Optional<Person> maxSalaryPerson = personList.stream().max(Comparator.comparingInt(Person::getSalary));
        System.out.println(maxSalaryPerson.get());
    }

输出萌新:

Person(name=Owen, salary=9500, age=25, sex=male, area=New York)

④ 计算 Integer 集合中大于6的元素的个数

@Test
    public void streamStudyTest6() {
        List<Integer> integerList = Lists.newArrayList(7, 6, 9, 3, 8, 2, 1);
        long result = integerList.stream().filter(num -> num > 6).count();
        System.out.println(result);
    }

输出:

3

4. 映射(map/flatMap)

映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为mapflatMap

  • map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

① 英文字符数组全部大写,整数数组全部+3

@Test
    public void streamStudyTest7() {
        String[] stringList = {"abcd", "bcdd", "defde", "fTr"};
        List<String> stringResult = Arrays.stream(stringList).map(String::toUpperCase).collect(Collectors.toList());
        System.out.println(stringResult);
        List<Integer> integerList = Lists.newArrayList(7, 6, 9, 3, 8, 2, 1);
        List<Integer> resultInteger = integerList.stream().map(num -> num + 3).collect(Collectors.toList());
        System.out.println(resultInteger);
    }

输出:

[ABCD, BCDD, DEFDE, FTR]
[10, 9, 12, 6, 11, 5, 4]

② 给萌新们普调 1000

@Test
    public void streamStudyTest8() {
        List<Person> personList = Lists.newArrayList(
                new Person("Tom", 8900, 23, "male", "New York"),
                new Person("Jack", 7000, 25, "male", "Washington"),
                new Person("Lily", 7800, 21, "female", "Washington"),
                new Person("Anni", 8200, 24, "female", "New York"),
                new Person("Owen", 9500, 25, "male", "New York"),
                new Person("Alisa", 7900, 26, "female", "New York")
        );
        // 不改变原来员工集合的方式
        List<Person> newPersonResult = personList.stream().map(person -> {
            Person newPerson = new Person(person.getName(), person.getSalary() + 1000, person.getAge(), person.getSex(), person.getArea());
            return newPerson;
        }).collect(Collectors.toList());
        System.out.println(newPersonResult);
        // 改变原来员工集合的方式
        List<Person> personResult = personList.stream().map(person -> {
            person.setSalary(person.getSalary() + 1000);
            return person;
        }).collect(Collectors.toList());
        System.out.println(personResult);
    }

输出萌新:

[Person(name=Tom, salary=9900, age=23, sex=male, area=New York), Person(name=Jack, salary=8000, age=25, sex=male, area=Washington), Person(name=Lily, salary=8800, age=21, sex=female, area=Washington), Person(name=Anni, salary=9200, age=24, sex=female, area=New York), Person(name=Owen, salary=10500, age=25, sex=male, area=New York), Person(name=Alisa, salary=8900, age=26, sex=female, area=New York)]
[Person(name=Tom, salary=9900, age=23, sex=male, area=New York), Person(name=Jack, salary=8000, age=25, sex=male, area=Washington), Person(name=Lily, salary=8800, age=21, sex=female, area=Washington), Person(name=Anni, salary=9200, age=24, sex=female, area=New York), Person(name=Owen, salary=10500, age=25, sex=male, area=New York), Person(name=Alisa, salary=8900, age=26, sex=female, area=New York)]

③ 将两个字符数组合并成一个新的字符数组

@Test
    public void streamStudyTest9() {
        List<String> stringList = Lists.newArrayList("m,k,l,a", "1,3,5,7");
        List<String> newStringList = stringList.stream().flatMap(string -> {
            // 将每个元素转换成一个Stream
            String[] split = string.split(",");
            Stream<String> stream = Arrays.stream(split);
            return stream;
        }).collect(Collectors.toList());
        System.out.println(newStringList);
    }

输出:

[m, k, l, a, 1, 3, 5, 7]

5. 规约(reduce)

归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。

① 求 integer 集合的元素之和、乘积和最大值

@Test
    public void streamStudyTest10() {
        List<Integer> integerList = Lists.newArrayList(1, 3, 2, 8, 11, 4);
        // 求和方式1
        Optional<Integer> result1 = integerList.stream().reduce((x, y) -> x + y);
        // 求和方式2
        Optional<Integer> result2 = integerList.stream().reduce(Integer::sum);
        // 求和方式3
        Integer result3 = integerList.stream().reduce(0, Integer::sum);
        // 求乘积
        Optional<Integer> result4 = integerList.stream().reduce((x, y) -> x * y);
        // 求最大值方式1
        Optional<Integer> result5 = integerList.stream().reduce((x, y) -> x > y ? x : y);
        // 求最大值方式2
        Integer result6 = integerList.stream().reduce(Integer.MIN_VALUE, Integer::max);

        System.out.println("list求和:" + result1.get() + "," + result2.get() + "," + result3);
        System.out.println("list求乘积:" + result4.get());
        System.out.println("list求最大值:" + result5.get() + "," + result6);
    }

输出:

list求和:29,29,29
list求乘积:2112
list求最大值:11,11

② 求萌新的薪资之和和最高薪资

@Test
    public void streamStudyTest11() {
        List<Person> personList = Lists.newArrayList(
                new Person("Tom", 8900, 23, "male", "New York"),
                new Person("Jack", 7000, 25, "male", "Washington"),
                new Person("Lily", 7800, 21, "female", "Washington"),
                new Person("Anni", 8200, 24, "female", "New York"),
                new Person("Owen", 9500, 25, "male", "New York"),
                new Person("Alisa", 7900, 26, "female", "New York")
        );
        // 求薪资之和方式1
        Optional<Integer> result1 = personList.stream().map(Person::getSalary).reduce(Integer::sum);
        // 求薪资之和方式2
        Integer result2 = personList.stream().reduce(0, (sum, person) -> sum += person.getSalary(), (sum1, sum2) -> sum1 + sum2);
        // 求薪资之和方式3
        Integer result3 = personList.stream().reduce(0, (sum, person) -> sum + person.getSalary(), Integer::sum);

        // 求最高薪资方式1
        Optional<Integer> result4 = personList.stream().map(Person::getSalary).reduce(Integer::max);
        // 求最高薪资方式2
        Integer result5 = personList.stream().reduce(0, (max, person) -> Math.max(max, person.getSalary()), (max1, max2) -> Math.max(max1, max2));

        System.out.println("薪资之和:" + result1.get() + "," + result2 + "," + result3);
        System.out.println("最高薪资:" + result4.get() + "," + result5);
    }

输出:

薪资之和:49300,49300,49300
最高薪资:9500,9500

6. 收集(collect)

collect,收集,可以说是内容最繁多、功能最丰富的部分了。从字面上去理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。

collect 主要依赖java.util.stream.Collectors类内置的静态方法。

6.1 归集(toList/toSet/toMap)

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toListtoSettoMap比较常用,另外还有toCollectiontoConcurrentMap等复杂一些的用法。

toList、toSet 和 toMap 案例:

@Test
    public void streamStudyTest12() {
        List<Integer> integerList = Lists.newArrayList(1, 6, 3, 4, 6, 7, 9, 6, 20);
        List<Integer> newList = integerList.stream().filter(x -> x % 2 == 0).collect(Collectors.toList());
        Set<Integer> newSet = integerList.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet());

        List<Person> personList = Lists.newArrayList(
                new Person("Anni", 8200, 24, "female", "New York"),
                new Person("Owen", 9500, 25, "male", "New York"),
                new Person("Alisa", 7900, 26, "female", "New York")
        );
        Map<String, Person> newMap = personList.stream().filter(person -> person.getSalary() > 8000).collect(Collectors.toMap(Person::getName, p -> p));

        System.out.println("toList:" + newList);
        System.out.println("toSet:" + newSet);
        System.out.print("toMap:");
        newMap.keySet().forEach(System.out::print);
    }

输出:

toList:[6, 4, 6, 6, 20]
toSet:[4, 20, 6]
toMap:Owen Anni
6.2 统计(count/averaging)

Collectors提供了一系列用于数据统计的静态方法:

  • 计数:count
  • 平均值:averagingIntaveragingLongaveragingDouble
  • 最值:maxByminBy
  • 求和:summingIntsummingLongsummingDouble
  • 统计以上所有:summarizingIntsummarizingLongsummarizingDouble

统计萌新人数、平均薪资、薪资总额和最高薪资:

@Test
    public void streamStudyTest13() {
        List<Person> personList = Lists.newArrayList(
                new Person("Anni", 8200, 24, "female", "New York"),
                new Person("Owen", 9500, 25, "male", "New York"),
                new Person("Alisa", 7900, 26, "female", "New York")
        );
        // 求萌新总数
        Long count = personList.stream().collect(Collectors.counting());
        // 求平均薪资
        Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
        // 求最高薪资
        Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
        // 求薪资之和
        Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
        // 一次性统计所有信息
        DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));

        System.out.println("萌新总数:" + count);
        System.out.println("萌新平均工资:" + average);
        System.out.println("萌新薪资总和:" + sum);
        System.out.println("萌新薪资所有统计:" + collect);
    }

输出:

萌新总数:3
萌新平均工资:8533.333333333334
萌新薪资总和:25600
萌新薪资所有统计:DoubleSummaryStatistics{count=3, sum=25600.000000, min=7900.000000, average=8533.333333, max=9500.000000}
6.3 分组(partitioningBy/groupingBy)
  • 分区:将 stream按条件分为两个 Map,比如员工按薪资是否高于 8000 分为两部分;
  • 分组:将 stream按元素某个属性分为多个 Map,比如员工按性别分组。有单级分组和多级分组。

将萌新薪资是否高于8k分为两部分,同时将萌新按性别和地区分组:

@Test
    public void streamStudyTest14() {
        List<Person> personList = Lists.newArrayList(
                new Person("Tom", 8900, 23, "male", "New York"),
                new Person("Jack", 7000, 25, "male", "Washington"),
                new Person("Lily", 7800, 21, "female", "Washington"),
                new Person("Anni", 8200, 24, "female", "New York"),
                new Person("Owen", 9500, 25, "male", "New York"),
                new Person("Alisa", 7900, 26, "female", "New York")
        );
        // 将萌新薪资是否高于8k分为两部分
        Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(person -> person.getSalary() > 8000));
        // 将萌新按照性别分组
        Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex));
        // 将萌新先按照性别分组,再按照地区分组
        Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));

        System.out.println("将萌新薪资是否高于8k分为两部分:");
        part.entrySet().forEach(System.out::println);
        System.out.println("将萌新按照性别分组:");
        group.entrySet().forEach(System.out::println);
        System.out.println("将萌新先按照性别分组,再按照地区分组:");
        System.out.println(group2);
    }

输出:

将萌新薪资是否高于8k分为两部分:
false=[Person(name=Jack, salary=7000, age=25, sex=male, area=Washington), Person(name=Lily, salary=7800, age=21, sex=female, area=Washington), Person(name=Alisa, salary=7900, age=26, sex=female, area=New York)]
true=[Person(name=Tom, salary=8900, age=23, sex=male, area=New York), Person(name=Anni, salary=8200, age=24, sex=female, area=New York), Person(name=Owen, salary=9500, age=25, sex=male, area=New York)]
将萌新按照性别分组:
female=[Person(name=Lily, salary=7800, age=21, sex=female, area=Washington), Person(name=Anni, salary=8200, age=24, sex=female, area=New York), Person(name=Alisa, salary=7900, age=26, sex=female, area=New York)]
male=[Person(name=Tom, salary=8900, age=23, sex=male, area=New York), Person(name=Jack, salary=7000, age=25, sex=male, area=Washington), Person(name=Owen, salary=9500, age=25, sex=male, area=New York)]
将萌新先按照性别分组,再按照地区分组:
{female={New York=[Person(name=Anni, salary=8200, age=24, sex=female, area=New York), Person(name=Alisa, salary=7900, age=26, sex=female, area=New York)], Washington=[Person(name=Lily, salary=7800, age=21, sex=female, area=Washington)]}, male={New York=[Person(name=Tom, salary=8900, age=23, sex=male, area=New York), Person(name=Owen, salary=9500, age=25, sex=male, area=New York)], Washington=[Person(name=Jack, salary=7000, age=25, sex=male, area=Washington)]}}
6.4 接合(joining)

joining可以将 stream 中的元素用特定的连接符(连接符为空,则直接连接)连接成一个字符串。

@Test
    public void streamStudyTest15() {
        List<Person> personList = Lists.newArrayList(
                new Person("Anni", 8200, 24, "female", "New York"),
                new Person("Owen", 9500, 25, "male", "New York"),
                new Person("Alisa", 7900, 26, "female", "New York")
        );
        String nameJoin = personList.stream().map(Person::getName).collect(Collectors.joining(","));
        System.out.println(nameJoin);
        List<String> charList = Lists.newArrayList("A", "B", "C");
        String english = charList.stream().collect(Collectors.joining());
        System.out.println(english);
    }

输出:

Anni,Owen,Alisa
ABC

6.5 归约(reducing)

Collectors 类提供的reducing方法,相比于 stream 本身的reduce方法,增加了对自定义归约的支持。

@Test
    public void streamStudyTest16() {
        List<Person> personList = Lists.newArrayList(
                new Person("Anni", 8200, 24, "female", "New York"),
                new Person("Owen", 8000, 25, "male", "New York"),
                new Person("Alisa", 4000, 26, "female", "New York")
        );
        // 每个萌新减去起征税点5k后的薪资总和
        Integer sumSalary = personList.stream().collect(Collectors.reducing(0, Person::getSalary,
                (sum, x) -> sum + (x > 5000 ? x - 5000 : 0)));
        System.out.println(sumSalary);
        // Stream的reduce
        Integer sumSalary2 = personList.stream().map(Person::getSalary).reduce(0, (sum, x) -> sum + (x > 5000 ? x - 5000 : 0));
        System.out.println(sumSalary2);
    }

输出:

6200
6200

7. 排序(sort)

sorted,中间操作。有两种排序:

  • sorted():自然排序,流中元素需实现 Comparable 接口;
  • sorted(Comparator com):Comparator 排序器自定义排序。
@Test
    public void streamStudyTest17() {
        List<Person> personList = Lists.newArrayList(
                new Person("Tom", 9000, 23, "male", "New York"),
                new Person("Jack", 7000, 25, "male", "Washington"),
                new Person("Lily", 8200, 21, "female", "Washington"),
                new Person("Anni", 8200, 24, "female", "New York"),
                new Person("Owen", 9000, 25, "male", "New York"),
                new Person("Alisa", 7900, 26, "female", "New York")
        );
        // 按工资升序排序(自然排序)
        List<String> sort1 = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName).collect(Collectors.toList());
        // 按工资倒序排序
        List<String> sort2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed()).map(Person::getName).collect(Collectors.toList());
        // 先按工资再按年龄升序排序
        List<String> sort3 = personList.stream().sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName).collect(Collectors.toList());
        // 先按工资再按年龄自定义排序(降序)
        List<String> sort4 = personList.stream().sorted((p1, p2) -> {
            if (p1.getSalary() != p2.getSalary()) {
                return p2.getSalary() - p1.getSalary();
            } else {
                return p2.getAge() - p1.getAge();
            }
        }).map(Person::getName).collect(Collectors.toList());

        System.out.println("按工资升序排序:" + sort1);
        System.out.println("按工资降序排序:" + sort2);
        System.out.println("先按工资再按年龄升序排序:" + sort3);
        System.out.println("先按工资再按年龄自定义降序排序:" + sort4);
    }

输出:

按工资升序排序:[Jack, Alisa, Lily, Anni, Tom, Owen]
按工资降序排序:[Tom, Owen, Lily, Anni, Alisa, Jack]
先按工资再按年龄升序排序:[Jack, Alisa, Lily, Anni, Tom, Owen]
先按工资再按年龄自定义降序排序:[Owen, Tom, Anni, Lily, Alisa, Jack]

8. 提取/组合

stream 流也可以进行合并、去重、限制、跳过等操作。

@Test
    public void streamStudyTest18() {
        String[] arr1 = {"a", "b", "c", "d"};
        String[] arr2 = {"d", "e", "f", "g"};

        Stream<String> stream1 = Stream.of(arr1);
        Stream<String> stream2 = Stream.of(arr2);
        // concat:合并两个流 distinct:去重
        List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
        // limit:限制从流中获得前n个数据
        List<Integer> collect = Stream.iterate(1, x -> x * 2).limit(10).collect(Collectors.toList());
        // skip:跳过前n个数据
        List<Integer> collect2 = Stream.iterate(1, x -> x * 2).skip(1).limit(5).collect(Collectors.toList());

        System.out.println("流合并:" + newList);
        System.out.println("limit:" + collect);
        System.out.println("skip:" + collect2);
    }

输出:

流合并:[a, b, c, d, e, f, g]
limit:[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
skip:[2, 4, 8, 16, 32]

9. 匹配元素(anyMatch/allMatch/noneMatch) [补充]

  • 是否匹配任一元素:anyMatch
  • 是否匹配所有元素:allMatch
  • 是否未匹配所有元素:noneMatch
@Test
    public void streamStudyTest19() {
        // 判断流中是否含有>10的元素
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
        boolean result1 = list.stream().anyMatch(x -> x > 5);
        // 判断流中元素是否全部>5
        boolean result2 = list.stream().allMatch(x -> x > 5);
        // 判断流中元素是否全部不满足>5
        boolean result3 = list.stream().noneMatch(x -> x > 5);
        
        System.out.println(result1);
        System.out.println(result2);
        System.out.println(result3);
    }

输出

true
false
false

对集合去重操作 [补充]

Stream的distinct方法

distinct()是Java 8中Stream提供的方法,返回的是由该流中不同元素组成的流。distinct()使用hashCode()eqauls()方法来获取不同的元素。

1. 对 List<String> 去重

@Test
    public void StringListDistinctTest() {
        List<String> stringList = Lists.newArrayList("ABC", "AB", "ABC", "B", "AB");
        List<String> result = stringList.stream().distinct().collect(Collectors.toList());
        result.forEach(System.out::println);
    }

去重后:

ABC
AB
B

2. 对 List<Object> 去重

@Data
@AllArgsConstructor
class Student {
    private String stuNo;
    private String name;
}
@Test
    public void ObjectListDistinctTest() {
        List<Student> studentList = Lists.newArrayList(new Student("001", "Tom"), new Student("002", "Jerry"),
                new Student("003", "Tom"), new Student("002", "Jerry"));
        List<Student> result = studentList.stream().distinct().collect(Collectors.toList());
        result.forEach(System.out::println);
    }

去重后:

Student(stuNo=001, name=Tom)
Student(stuNo=002, name=Jerry)
Student(stuNo=003, name=Tom)

3. 根据 List<Object> 中 Object 某个属性的去重

  • CollectionCollection是集合类接口,ListSetQueue是它的子接口;
  • Collector:就是收集器,也是一个接口。它的工具类Collectors提供了很多工厂方法创建的收集器;
  • Collectors.toCollecction()方法:返回一个将累积输入的元素收集到新集合的Collector
  • Collectors.collectingAndThen()方法:将元素放到新集合,再通过Function处理一下(比如转为list)。

大神指点orz:JDK8 Stream操作 collectingAndThen--根据对象的属性进行去重操作

@Test
    public void ObjectListDistinctByProperty() {
        List<Student> studentList = Lists.newArrayList(new Student("001", "Tom"), new Student("002", "Jerry"),
                new Student("003", "Tom"), new Student("002", "Jerry"));
        List<Student> result = studentList.stream().collect(
                Collectors.collectingAndThen(
                        Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Student::getName))), ArrayList::new)
        );
        result.forEach(System.out::println);
    }

去重后:

Student(stuNo=002, name=Jerry)
Student(stuNo=001, name=Tom)

双冒号(::)为方法引用运算符,而它所在的表达式被称为方法引用。如果 Lambda 所要实现的方案,已经在其他方法存在相同方案,那么则可以使用方法引用。

打的不错:Lambda表达式--方法引用(::)

抽取集合中对象的属性转为 list/map [常用]

1. 抽取对象的 name 得到 name 的 list 集合

@Test
    public void getNameList() {
        List<Student> studentList = Lists.newArrayList(new Student("001", "Tom"), new Student("002", "Jerry"),
                new Student("003", "Tom"), new Student("002", "Jerry"));
        List<String> result = studentList.stream().map(Student::getName).collect(Collectors.toList());
        result.forEach(System.out::println);
    }

输出 name 的 list 集合:

Tom
Jerry
Tom
Jerry

2. 抽取对象的 stuNo 作为 key,name 作为 value 转化为 map 集合

  • filter() 方法作用是过滤掉名字为空的对象,当对象的名字为 null 时,会出现 NPE 空指针异常!
  • (k1,k2)->k2 意思是遇到相同的 key 时取第二个值;
  • (k1,k2)->k1 意思是遇到相同的 key 时取第一个值;
  • 如果无(k1,k2)->k?时,如果遇到相同的 key 时,会出现 IllegalStateException 异常!
@Test
    public void listToMap() {
        List<Student> studentList = Lists.newArrayList(new Student("001", "Tom"), new Student("002", "Jerry"),
                new Student("001", "Jerry"), new Student("003", null));
        Map<String, String> result = studentList.stream()
                .filter(stu -> stu.getName() != null)
                .collect(Collectors.toMap(Student::getStuNo, Student::getName, (k1, k2) -> k1));
        result.values().forEach(System.out::println);
    }

输出 map 集合的 value:

Tom
Jerry

3. 抽取对象的属性得到新对象的 list/map 集合

@Test
    public void multListOper() {
        List<Person> personList = Lists.newArrayList(
                new Person("Tom", 9000, 23, "male", "New York"),
                new Person("Jack", 7000, 25, "male", "Washington"),
                new Person("Lily", 8200, 21, "female", "Washington"),
                new Person("Anni", 8200, 24, "female", "New York"),
                new Person("Owen", 9000, 25, "male", "New York"),
                new Person("Alisa", 7900, 26, "female", "New York")
        );
        List<LWPerson> lwList = personList.stream().map(person -> {
            LWPerson lw = new LWPerson();
            lw.setName(person.getName());
            lw.setSalary(person.getSalary());
            return lw;
        }).collect(Collectors.toList());
        lwList.forEach(System.out::println);

        Map<String, LWPerson> lwMap = personList.stream().collect(Collectors.toMap(Person::getName, person -> {
            LWPerson lw = new LWPerson();
            lw.setName(person.getName());
            lw.setSalary(person.getSalary());
            return lw;
        }, (k1, k2) -> k1));
        lwMap.entrySet().forEach(System.out::println);
    }

输出 list/map :

LWPerson(name=Tom, salary=9000)
LWPerson(name=Jack, salary=7000)
LWPerson(name=Lily, salary=8200)
LWPerson(name=Anni, salary=8200)
LWPerson(name=Owen, salary=9000)
LWPerson(name=Alisa, salary=7900)
Tom=LWPerson(name=Tom, salary=9000)
Owen=LWPerson(name=Owen, salary=9000)
Anni=LWPerson(name=Anni, salary=8200)
Alisa=LWPerson(name=Alisa, salary=7900)
Jack=LWPerson(name=Jack, salary=7000)
Lily=LWPerson(name=Lily, salary=8200)

常用Stream流操作文章引用:

  1. 分享几种 Java8 中通过 Stream 对列表进行去重的方法
  2. Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合
  3. JDK8 Stream 详细使用