2023/06/26

Java Stream API

Collection 是程式中存放於內部記憶體的資料結構,而 Stream 是一種資料流 pipeline,可以依照需要使用這些內部記憶體的資料。

Stream 的 operation 有兩類:intermediate 或是 terminal,intermediate opertaion 會回傳 stream,故能夠以 chain 的方式依序串接多個 intermediate operation,針對資料進行運算。terminal operation 會於計算後,回傳某個類別的結果。

Stream operation 有循序跟平行化處理兩種,平行化的 operations 能夠在多個 thread 同時運算,能充分運用多核心處理器,加速運算的過程。

Stream 的資料來源是 java.util.Collection,例如 List, Set。Map 無法直接支援,但能分別對 keys, values 或 entries 建立 Stream。

建立 Stream

從 data collections 產生 Stream 的方法

    private static void createStream() {
        // Stream.of
        System.out.println("Stream.of");
        Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9);
        stream.forEach(p -> System.out.print(p + " "));
        System.out.println("");

        // Stream.of(array)
        System.out.println("Stream.of(array)");
        Stream<Integer> stream2 = Stream.of( new Integer[]{1,2,3,4,5,6,7,8,9} );
        stream2.forEach(p -> System.out.print(p + " "));
        System.out.println("");

        // List.stream()
        System.out.println("List.stream()");
        List<Integer> list = new ArrayList<Integer>();
        for(int i = 1; i< 10; i++){
            list.add(i);
        }
        Stream<Integer> stream3 = list.stream();
        stream3.forEach(p -> System.out.print(p + " "));
        System.out.println("");

        // Stream.generate() or Stream.iterate()
        // 透過 generator function 產生 Stream elements
        // 以 limit() 限制 element 個數
        System.out.println("Stream.generate()");
        Stream<Integer> randomNumbers = Stream
                .generate(() -> (new Random()).nextInt(100));
        randomNumbers.limit(10).forEach(s -> System.out.print(s + " "));
        System.out.println("");

        //  Stream of String chars or tokens
        System.out.println("Stream of String chars");
        IntStream stream5 = "12345_abcdefg".chars();
        stream5.forEach(p -> System.out.print(p + " "));
        System.out.println("");
        System.out.println("Stream of tokens");
        Stream<String> stream6 = Stream.of("A$B$C".split("\\$"));
        stream6.forEach(p -> System.out.print(p + " "));
        System.out.println("");
    }

Stream Collector

透過 Collector 可將 Stream 裡面的 elements 再轉換為 Collection

    private static void streamCollector() {
        // List 轉換為 Stream
        List<Integer> list = new ArrayList<Integer>();
        for(int i = 1; i< 10; i++){
            list.add(i);
        }
        Stream<Integer> stream = list.stream();

        // 用 collect 再將 Stream 轉換為 List
        System.out.println("collect");
        List<Integer> evenNumbersList = stream.filter(i -> i%2 == 0)
                .collect(Collectors.toList());
        System.out.print(evenNumbersList);
        System.out.println("");

        // 用 collect 再將 Stream 轉換為 Array
        System.out.println("toArray");
        Stream<Integer> stream2 = list.stream();
        Integer[] evenNumbersArr = stream2.filter(i -> i%2 == 0).toArray(Integer[]::new);
        for (Integer i : evenNumbersArr) {
            System.out.print(i + " ");
        }
        System.out.println("");
    }

Operations

Intermediate Operations

method description
filter() 條件過濾
map() 對每個 elements 執行某個運算
flatMap() 將多個Stream連接成一個Stream
distinct() 刪除相同的 elements
sorted() 排序
peek() 用在debug
limit() 限制回傳的 elements 數量
skip() 略過前面幾個 elements

java sample

    private static void intermediateOperation() {
        List<String> names = new ArrayList<>();
        names.add("Allen");
        names.add("George");
        names.add("Anderson");
        names.add("Danny");

        // filter 條件過濾
        System.out.println("filter");
        names.stream().filter((s) -> s.startsWith("A"))
                .forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("");

        // map  對每個 elements 執行某個運算
        System.out.println("map");
        names.stream().filter((s) -> s.startsWith("A"))
                .map(String::toUpperCase)
                .forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("");

        // flatMap  將多個Stream連接成一個Stream
        List<String> names2 = new ArrayList<>();
        names2.add("Jim");
        System.out.println("flatMap");
        List<String> flapMapStream = Stream.of(names, names2).flatMap(u -> u.stream()).collect(Collectors.toList());
        System.out.println(flapMapStream);
        System.out.println("");
        System.out.println("");

        // distinct
        // 刪除相同的 elements
        List<String> names3 = new ArrayList<>();
        names3.add("Jim");
        names3.add("Jimmy");
        names3.add("Jim");
        System.out.println("distinct");
        names3.stream()
                .distinct()
                .forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("");

        //sorted  排序
        System.out.println("sorted");
        names.stream()
                .sorted()
                .forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("");

        // peek 用在debug
        System.out.println("peek");
        List res = names.stream().filter((s) -> s.startsWith("A"))
                .peek(e -> System.out.println("filter res: " + e))
                .map(String::toUpperCase)
                .peek(e -> System.out.println("map res: " + e))
                .collect(Collectors.toList());
        System.out.println(res);
        System.out.println("");
        System.out.println("");

        // limit
        // 限制回傳的 elements 數量
        System.out.println("limit");
        List res2 = names.stream().filter((s) -> s.startsWith("A"))
                .map(String::toUpperCase)
                .limit(1)
                .collect(Collectors.toList());
        System.out.println(res2);
        System.out.println("");
        System.out.println("");

        // skip
        // 略過前面幾個 elements
        System.out.println("skip");
        List res3 = names.stream().filter((s) -> s.startsWith("A"))
                .map(String::toUpperCase)
                .skip(1)
                .collect(Collectors.toList());
        System.out.println(res3);
        System.out.println("");
        System.out.println("");
    }

Terminal Operations

method description
forEach 對 steam 所有 elements 進行運算
forEachOrdered 會依照原本 stream 元素的順序輸出,平行化 stream 後,順序也會固定
toArray 轉換為 Array
reduce 把一個Stream的所有元素按照聚合函數聚合成一個結果
collect 透過 Collector 可將 Stream 裡面的 elements 再轉換為 Collection
min 找最小元素
max 找最大元素
count 元素數量
anyMatch 檢查是否有任何一個元素,滿足給定的條件
allMatch 檢查是否有所有的元素,都滿足給定的條件
nonMatch 檢查是否沒有任何一個元素,滿足給定的條件
findFirst 回傳 stream 的第一個元素
findAny 回傳 stream 的任一個元素
    private static void terminalOperation() {
        List<String> names = new ArrayList<>();
        names.add("Allen");
        names.add("George");
        names.add("Anderson");
        names.add("Danny");

        // forEach
        System.out.println("forEach");
        names.stream().forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("");

        // forEachOrdered
        System.out.println("1. Sequential Stream");
        System.out.println("1.1 forEach 會依照原本的順序輸出");
        names.stream().forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("1.2 forEachOrdered 會依照原本的順序輸出");
        names.stream().forEachOrdered(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("2. Parallel Stream");
        System.out.println("2.1 forEach 輸出順序不固定");
        names.stream().parallel().forEach(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("2.2 forEachOrdered 會依照原本的順序輸出");
        names.stream().parallel().forEachOrdered(n -> System.out.print(n + " "));
        System.out.println("");
        System.out.println("");

        // toArray
        System.out.println("toArray");
        String[] nameArray = names.stream().toArray(String[]::new);
        for( String n: nameArray) {
            System.out.print(n+" ");
        }
        System.out.println("");
        System.out.println("");

        // reduce 傳入 BinaryOperator
        System.out.println("reduce");
        int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).reduce(0, (s, n) -> s + n);
        int mul = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).reduce(1, (m, n) -> m * n);
        System.out.println("累加 sum="+sum);
        System.out.println("連乘 mul="+mul);
        System.out.println("");
        System.out.println("");

        // collect
        System.out.println("collect");
        List<String> filterNames = names.stream().filter((s) -> s.startsWith("A"))
                .collect(Collectors.toList());
        for( String n: filterNames) {
            System.out.print(n+" ");
        }
        System.out.println("");
        System.out.println("");

        // min, max
        System.out.println("min, max");
        Optional<String> minName = names.stream()
                .min(Comparator.comparing(String::valueOf));
        if( minName.isPresent() ) {
            System.out.println("min = "+minName.get());
        }
        Optional<String> maxName = names.stream()
                .max(Comparator.comparing(String::valueOf));
        if( maxName.isPresent() ) {
            System.out.println("max = "+maxName.get());
        }
        System.out.println("");
        System.out.println("");

        // count
        System.out.println("count");
        long count = names.stream().filter((s) -> s.startsWith("A"))
                .count();
        System.out.println("count="+count);
        System.out.println("");
        System.out.println("");

        // anyMatch allMatch noneMatch
        System.out.println("anyMatch allMatch noneMatch");
        boolean anyMatch = names.stream()
                .anyMatch(n -> Character.isUpperCase(n.charAt(0)));
        boolean allMatch = names.stream()
                .allMatch(n -> Character.isUpperCase(n.charAt(0)));
        boolean noneMatch = names.stream()
                .noneMatch(n -> Character.isUpperCase(n.charAt(0)));
        System.out.println("anyMatch="+anyMatch);
        System.out.println("allMatch="+allMatch);
        System.out.println("noneMatch="+noneMatch);
        System.out.println("");
        System.out.println("");

        // findFirst  findAny
        System.out.println("findFirst  findAny");
        Optional<String> findFirst = names.stream().filter((s) -> s.startsWith("A"))
                .findFirst();
        if( findFirst.isPresent() ) {
            System.out.println("findFirst = "+findFirst.get());
        }
        Optional<String> findAny = names.stream().filter((s) -> s.startsWith("A"))
                .findAny();
        if( findAny.isPresent() ) {
            System.out.println("findAny = "+findAny.get());
        }
        System.out.println("");
        System.out.println("");
    }

parallel

parallelStream() 及 stream().parallel() 可將 stream 平行化

    private static void parallel() {
        // List.stream()
        // List.parallelStream()
        System.out.println("List.stream()");
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 1; i < 10; i++) {
            list.add(i);
        }
        Stream<Integer> stream3 = list.stream();
        stream3.forEach(p -> System.out.print(p + " "));
        System.out.println("");
        System.out.println("List.parallelStream()");
        Stream<Integer> stream4 = list.parallelStream();
        stream4.forEach(p -> System.out.print(p + " "));
        System.out.println("");

        // stream().parallel()
        System.out.println("stream().parallel()");
        Stream<Integer> stream5 = list.stream().parallel();
        stream5.forEach(p -> System.out.print(p + " "));
        System.out.println("");
    }

References

[JAVA] Java 8 Streams API

Java Stream API (with Examples) - HowToDoInJava

Understanding Java 8 Streams API - amitph

10 Examples of Stream API in Java 8 - count + filter + map + distinct + collect() Examples | Java67

Java 8 Stream | 菜鸟教程

Difference Between parallelStream() and stream().parallel() in Java | Baeldung

沒有留言:

張貼留言