Java 8 stream 實戰

概述

平時工做用python的機會比較多,習慣了python函數式編程的簡潔和優雅。切換到java後,對於數據處理的『冗長代碼』仍是有點不習慣的。有幸的是,Java8版本後,引入了Lambda表達式和流的新特性,當流和Lambda表達式結合起來一塊兒使用時,由於流申明式處理數據集合的特色,可讓代碼變得簡潔易讀。幸福感爆棚,有沒有!java

本文主要列舉一些stream的使用例子,並附上相應代碼。python

實例

先準備測試用的數據,這裏簡單聲明瞭一個Person類,有名稱和年齡兩個屬性,採用 lombok 註解方式節省了一些模板是的代碼,讓代碼更加簡潔。git

@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的數據集合spring

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比較簡單,這裏不贅述了,直接看代碼app

flatmap

說明

  • 和map不一樣的是,flatmap是個一對多的映射,而後把多個打平
  • flatmap接收的函數參數也是Fuction,可是還和map的入參Function相比,能夠看到返回值不一樣。flatmap,返回的是Stream<R>,map返回的是R,這就是上面說的一對多映射

示例圖

從圖中也能夠看到一對多映射,例如紅色圓圈通過flapmap後變成了2個(一個菱形、一個方形)ide

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

代碼下載

參考文檔

相關文章
相關標籤/搜索