Java 8 stream 實戰

概述

平時工作用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的數據集合

image

代碼

    @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方法後生成的數據集合,數據個數保持不變,即一對一映射

img

代碼

@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個(一個菱形、一個方形)

img

代碼

    @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來解決

img

代碼

    @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 兩類還是會存在)

代碼下載

參考文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章