Java 8 stream 实战

Wesley13
• 阅读 636

概述

平时工作用python的机会比较多,习惯了python函数式编程的简洁和优雅。切换到java后,对于数据处理的『冗长代码』还是有点不习惯的。有幸的是,Java8版本后,引入了Lambda表达式和流的新特性,当流和Lambda表达式结合起来一起使用时,因为流申明式处理数据集合的特点,可以让代码变得简洁易读。幸福感爆棚,有没有!

本文主要列举一些stream的使用例子,并附上相应代码。

实例

先准备测试用的数据,这里简单声明了一个Person类,有名称和年龄两个属性,采用 lombok 注解方式节省了一些模板是的代码,让代码更加简洁。

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    private static class Person {
        private String name;
        private Integer age;
    }

    private List<Person> initPersonList() {
        return Lists.newArrayList(new Person("Tom", 18),
                new Person("Ben", 22),
                new Person("Jack", 16),
                new Person("Hope", 4),
                new Person("Jane", 19),
                new Person("Hope", 16));
    }

filter

说明

  • 遍历数据并检查其中的元素是否符合要求,不符合要求的过滤掉
  • filter接受一个函数作为参数(Predicate),该函数用Lambda表达式表示,返回true or false,返回false的数据会被过滤

示例图

数据集合过Predicate方法,留下返回true的数据集合

Java 8 stream 实战

代码

    @Test
    public void filterTest() {
        List<Person> personList = initPersonList();
        // 过滤出年龄大于8的数据
        List<Person> result = personList.stream().filter(x -> x.getAge() > 18).collect(Collectors.toList());
        log.info(JsonUtils.toJson(result));
        // filter 链式调用实现 and
        result =
                personList.stream().filter(x -> x.getAge() > 18).filter(x -> x.getName().startsWith("J")).collect(Collectors.toList());
        log.info(JsonUtils.toJson(result));
        // 通过 Predicate 实现 or
        Predicate<Person> con1 = x -> x.getAge() > 18;
        Predicate<Person> con2 = x -> x.getName().startsWith("J");
        result =
                personList.stream().filter(con1.or(con2)).collect(Collectors.toList());
        log.info(JsonUtils.toJson(result));
    }

以上是filter的例子,可以使用链式调用实现『与』的逻辑。通过声明Predicate,并使用 or 实现『或』逻辑

map

说明

  • map生成的是个一对一映射,for的作用
  • map接收一个函数做为参数,此函数为Function,执行方法R apply(T t),因此map是一对一映射

示例图

数据集合经过map方法后生成的数据集合,数据个数保持不变,即一对一映射

Java 8 stream 实战

代码

@Test
    public void mapTest() {
        List<Person> personList = initPersonList();
        List<String> result = personList.stream().map(Person::getName).collect(Collectors.toList());
        log.info(JsonUtils.toJson(result));
        Set<String> nameSet =
                personList.stream().filter(x -> x.getAge() < 20).map(Person::getName).collect(Collectors.toSet());
        log.info(JsonUtils.toJson(nameSet));
    }

map比较简单,这里不赘述了,直接看代码

flatmap

说明

  • 和map不同的是,flatmap是个一对多的映射,然后把多个打平
  • flatmap接收的函数参数也是Fuction,但是还和map的入参Function相比,可以看到返回值不同。flatmap,返回的是Stream,map返回的是R,这就是上面说的一对多映射

示例图

从图中也可以看到一对多映射,例如红色圆圈经过flapmap后变成了2个(一个菱形、一个方形)

Java 8 stream 实战

代码

    @Test
    public void flatMapTest() {
        List<Person> personList = initPersonList();
        List<String> result =
                personList.stream().flatMap(x -> Arrays.stream(x.getName().split("n"))).collect(Collectors.toList());
        log.info(JsonUtils.toJson(result));
    }

以上代码打印:["Tom","Be","Jack","Hope","Ja","e","Hope"],对每个人的姓名用字母n做了切分

reduce

说明

  • 是个多对一的映射,概念和hadoop中常用的map-reduce中的reduce相同
  • reduce接收两个参数,一个是identity(恒等值,比如累加计算中的初始累加值),另一个是BiFunction,调用方法R apply(T t, U u),即把两个值reduce为一个值

示例图

下面是1+2+3+4+5的例子,可以用reduce来解决

Java 8 stream 实战

代码

    @Test
    public void reduceTest() {
        Integer sum = Stream.of(1, 2, 3, 4, 5).reduce(0, Integer::sum);
        Assert.assertEquals(15, sum.intValue());
        sum = Stream.of(1, 2, 3, 4, 5).reduce(10, Integer::sum);
        Assert.assertEquals(25, sum.intValue());
        String result = Stream.of("1", "2", "3")
                .reduce("0", (x, y) -> (x + "," + y));
        log.info(result);
    }

对应示例图的代码实现,有数字求和的例子和字符串拼接的例子

collect

collect在流中生成列表,map,等常用的数据结构。常用的有toList(), toSet(), toMap()

下面代码列举了几个常用的场景

    @Test
    public void collectTest() {
        List<Person> personList = initPersonList();
        // 以name为key, 建立name-person的映射,如果key重复,后者覆盖前者
        Map<String, Person> result = personList.stream().collect(Collectors.toMap(Person::getName, x -> x,
                (x, y) -> y));
        log.info(JsonUtils.toJson(result));
        // 以name为key, 建立name-person_list的映射,即一对多
        Map<String, List<Person>> name2Persons = personList.stream().collect(Collectors.groupingBy(Person::getName));
        log.info(JsonUtils.toJson(name2Persons));
        String name = personList.stream().map(Person::getName).collect(Collectors.joining(",", "{", "}"));
        Assert.assertEquals("{Tom,Ben,Jack,Hope,Jane,Hope}", name);

        // partitioningBy will always return a map with two entries, one for where the predicate is true and one for where it is false. It is possible that both entries will have empty lists, but they will exist.
        List<Integer> integerList = Arrays.asList(3, 4, 5, 6, 7);
        Map<Boolean, List<Integer>> result1 = integerList.stream().collect(Collectors.partitioningBy(i -> i < 3));
        log.info(JsonUtils.toJson(result1));

        result1 = integerList.stream().collect(Collectors.groupingBy(i -> i < 3));
        log.info(JsonUtils.toJson(result1));
    }
  1. 建立name-person的映射,如果key重复,后者覆盖前者。Collectors.toMap的第三个参数就是BiFunction,和reduce中的一样,输入两个参数,返回一个参数。(x, y) -> y 就是(oldValue, newValue) -> oldValue,如果不加这个方法,那么当出现map的key重复,会直接抛异常
  2. 将list转化为一对多的map,可以采用Collectors.groupingBy,上述例子就是用person的name做为key,建议一对多映射关系
  3. 这里提到了groupingBypartitioningBy的区别,前者是根据某个key进行分组,后者是分类,看他们的入参就明白了,groupingBy的入参是FunctionpartitioningBy的入参是Predicate,即返回的是true/false。所以partitioningBy的key就是两类,true和false(即使存在空列表,true 和 false 两类还是会存在)

代码下载

  • Java 8 stream 实战:代码 commit,源码下载

参考文档

  • 使用 Stream API 优化代码
  • Java 8 - Stream 集合操作快速上手
点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
java 8 lambda表达式中的异常处理
java8lambda表达式中的异常处理简介java8中引入了lambda表达式,lambda表达式可以让我们的代码更加简介,业务逻辑更加清晰,但是在lambda表达式中使用的FunctionalInterface并没有很好的处理异常,因为JDK提供的这些FunctionalInterface通常都是没有抛出异常的,这意味着需要我们自
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
kenx kenx
3年前
Java8 新特性 Stream Api 之集合遍历
前言随着java版本的不断更新迭代,java开发也可以变得甜甜的,最新版本都到java11了,但是后面版本也是不在提供商用支持,需要收费,但是java8依然是持续免费更新使用的,后面版本也更新很快眼花缭乱,所以稳定使用还是用java8把既可以体验到新功能,又不需要,烦恼升级带来的bug新特性比较新的的特性就是流Stream,和lambda表达式图上
Wesley13 Wesley13
3年前
Java8特性
Java8又称jdk1.8。主要新特性:Lambda表达式 −Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。方法引用 −方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
Wesley13 Wesley13
3年前
Java 8中你可能没听过的10个新特性
lambda表达式,lambda表达式,还是lambda表达式。一提到Java8就只能听到这个,但这不过是其中的一个新功能而已,Java8还有许多新的特性——有一些功能强大的新类或者新的用法,还有一些功能则是早就应该加到Java里了。这里我准备介绍它的10个我个人认为非常值得了解的新特性。总会有一款适合你的,开始来看下吧。default方法
Wesley13 Wesley13
3年前
Java 8新特性之Stream 概念
Java8中有两大最为重要的改变。第一个是Lambda表达式;另外一个则是StreamAPI(java.util.stream.\)。Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用StreamAPI对集合数据进行操作,就类似于使用SQL执行
Wesley13 Wesley13
3年前
Java8—一万字的Lambda表达式的详细介绍与应用案例
  基于Java8详细介绍了lambda表达式的语法与使用,以及方法引用、函数式接口、lambda复合等Java8的新特性!文章目录1Lambda的概述2函数式接口2.1Consumer消费型接口2.2Supplier供给型接口2.3Function<T,R函数型接口
Wesley13 Wesley13
3年前
Java8 新特性之集合操作Stream
Java8新特性之集合操作StreamStream简介Java8引入了全新的StreamAPI。这里的Stream和I/O流不同,它更像具有Iterable的集合类,但行为和集合类又有所不同。stream是对集合对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。
Wesley13 Wesley13
3年前
Java8新特性学习
1简述公司自年初终于开始使用java8作为项目的标准jdk,在开发过程中,逐渐认识到java8的很多新特性,确实很方便.其中内容的核心,在于函数式编程,即将函数本身作为对象参数去处理.其中涉及到三个关键新特性:1.lambda表达式(及函数式接口)2.stream3.方法引用这三个新特性的使用是相辅相
Stream流处理快速上手最佳实践 | 京东物流技术团队
一引言JAVA1.8得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。Stream可以看作是流水线上的一个工序。在