《Java 高階篇》八:新特性

ACatSmiling發表於2024-10-02

Author: ACatSmiling

Since: 2024-10-01

Lambda 表示式

Lambda 是一個匿名函式,我們可以把 Lambda 表示式理解為是一段可以傳遞的程式碼(將程式碼像資料一樣進行傳遞)。使用它可以寫出更簡潔、更靈活的程式碼。作為一種更緊湊的程式碼風格,使 Java 的語言表達能力得到了提升。

Lambda 表示式:在 Java 8 語言中引入的一種新的語法元素和運算子。這個運算子為 "->",該運算子被稱為 Lambda 運算子或箭頭運算子。它將 Lambda 分為兩個部分:

  • 左側:指定了 Lambda 表示式需要的引數列表。
  • 右側:指定了 Lambda 體,是抽象方法的實現邏輯,也即 Lambda 表示式要執行的功能。

語法格式:

image-20210409215450100

型別推斷:上述 Lambda 表示式中的引數型別都是由編譯器推斷得出的。Lambda 表示式中無需指定型別,程式依然可以編譯,這是因為 javac 根據程式的上下文,在後臺推斷出了引數的型別。Lambda 表示式的型別依賴於上下文環境,是由編譯器推斷出來的。這就是所謂的型別推斷。

public class LambdaTest {
    // 語法格式三:資料型別可以省略,因為可由編譯器推斷得出,稱為 "型別推斷"
    @Test
    public void test3() {
        Consumer<String> con1 = (String s) -> {
            System.out.println(s);
        };
        con1.accept("一個是聽的人當真了,一個是說的人當真了");

        System.out.println("*******************");

        Consumer<String> con2 = (s) -> {
            System.out.println(s);
        };
        con2.accept("一個是聽的人當真了,一個是說的人當真了");
    }

    @Test
    public void test4() {
        ArrayList<String> list = new ArrayList<>();// 型別推斷,ArrayList<String> list = new ArrayList<String>();

        int[] arr = {1, 2, 3};// 型別推斷,int[] arr = new int[]{1, 2, 3};
    }
}

Lambda 示例:

/**
 * Lambda 表示式的使用
 *
 * 1. 舉例:(o1,o2) -> Integer.compare(o1,o2);
 * 2. 格式:
 *      ->: lambda 運算子或箭頭運算子
 *      ->左邊:lambda 形參列表(其實就是介面中的抽象方法的形參列表)
 *      ->右邊:lambda 體(其實就是重寫的抽象方法的方法體)
 *
 * 3. Lambda 表示式的使用:(分為 6 種情況介紹)
 *
 *    總結:
 *    ->左邊:lambda 形參列表的引數型別可以省略(型別推斷);如果 lambda 形參列表只有一個引數,其一對 () 也可以省略,其他情況不能省略
 *    ->右邊:lambda 體應該使用一對 {} 包裹;如果 lambda 體只有一條執行語句(也可能是 return 語句),省略這一對 {} 和 return 關鍵字
 *
 * 4. Lambda表示式的本質:作為函式式介面的例項
 *
 * 5. 如果一個介面中,只宣告瞭一個抽象方法,則此介面就稱為函式式介面。我們可以在一個介面上使用 @FunctionalInterface 註解,這樣做可以檢查它是否是一個函式式介面
 *
 * 6. 所有以前用匿名實現類表示的現在都可以用 Lambda 表示式來寫
 */
public class LambdaTest {
    // 語法格式一:無參,無返回值
    @Test
    public void test1() {
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("我愛北京天安門");
            }
        };
        r1.run();

        System.out.println("***********************");

        Runnable r2 = () -> {
            System.out.println("我愛北京故宮");
        }
        r2.run();
    }

    // 語法格式二:Lambda 需要一個引數,但是沒有返回值
    @Test
    public void test2() {
        Consumer<String> con = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        con.accept("謊言和誓言的區別是什麼?");

        System.out.println("*******************");

        Consumer<String> con1 = (String s) -> {
            System.out.println(s);
        }
        con1.accept("一個是聽的人當真了,一個是說的人當真了");
    }

    // 語法格式三:資料型別可以省略,因為可由編譯器推斷得出,稱為 "型別推斷"
    @Test
    public void test3() {
        Consumer<String> con1 = (String s) -> {
            System.out.println(s);
        };
        con1.accept("一個是聽的人當真了,一個是說的人當真了");

        System.out.println("*******************");

        Consumer<String> con2 = (s) -> {
            System.out.println(s);
        };
        con2.accept("一個是聽的人當真了,一個是說的人當真了");
    }

    // 語法格式四:Lambda 若只需要一個引數時,引數的小括號可以省略
    @Test
    public void test4() {
        Consumer<String> con1 = (s) -> {
            System.out.println(s);
        };
        con1.accept("一個是聽的人當真了,一個是說的人當真了");

        System.out.println("*******************");

        Consumer<String> con2 = s -> {
            System.out.println(s);
        };
        con2.accept("一個是聽的人當真了,一個是說的人當真了");
    }

    // 語法格式五:Lambda 需要兩個或以上的引數,多條執行語句,並且可以有返回值
    @Test
    public void test5() {
        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                System.out.println(o1);
                System.out.println(o2);
                return o1.compareTo(o2);
            }
        };
        System.out.println(com1.compare(12, 21));

        System.out.println("*****************************");

        Comparator<Integer> com2 = (o1, o2) -> {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        };
        System.out.println(com2.compare(12, 6));
    }

    // 語法格式六:當 Lambda 體只有一條語句時,return 與大括號若有,都可以省略
    @Test
    public void test6() {
        Comparator<Integer> com1 = (o1, o2) -> {
            return o1.compareTo(o2);
        };
        System.out.println(com1.compare(12, 6));

        System.out.println("*****************************");

        Comparator<Integer> com2 = (o1, o2) -> o1.compareTo(o2);
        System.out.println(com2.compare(12, 21));
    }

    @Test
    public void test7() {
        Consumer<String> con1 = s -> {
            System.out.println(s);
        };
        con1.accept("一個是聽的人當真了,一個是說的人當真了");

        System.out.println("*****************************");

        Consumer<String> con2 = s -> System.out.println(s);
        con2.accept("一個是聽的人當真了,一個是說的人當真了");
    }
}

函式式(Functional)介面

什麼是函式式(Functional)介面

  • 只包含一個抽象方法的介面,稱為函式式介面。
  • 你可以透過 Lambda 表示式來建立該介面的物件。(若 Lambda 表示式丟擲一個受檢異常(即:非執行時異常),那麼該異常需要在目標介面的抽象方法上進行宣告。)
  • 我們可以在一個介面上使用@FunctionalInterface註解,這樣做可以檢查它是否是一個函式式介面。同時 javadoc 也會包含一條宣告,說明這個介面是一個函式式介面。
  • java.util.function包下定義了 Java 8 的豐富的函式式介面。

如何理解函式式介面:

  • Java 從誕生日起就是一直倡導 "一切皆物件",在 Java 裡面物件導向(OOP)程式設計是一切。但是隨著 Python、Scala 等語言的興起和新技術的挑戰,Java 不得不做出調整以便支援更加廣泛的技術要求,也即Java 不但可以支援 OOP,還可以支援 OOF(面向函式程式設計)
  • 在函數語言程式設計語言當中,函式被當做一等公民對待。在將函式作為一等公民的程式語言中,Lambda 表示式的型別是函式。但是在 Java 8 中,有所不同。在 Java 8 中,Lambda 表示式是物件,而不是函式,它們必須依附於一類特別的物件型別——函式式介面。
  • 簡單的說,在 Java 8 中,Lambda 表示式就是一個函式式介面的例項。這就是 Lambda 表示式和函式式介面的關係。也就是說,只要一個物件是函式式介面的例項,那麼該物件就可以用 Lambda 表示式來表示。
  • 所有以前用匿名實現類表示的現在都可以用 Lambda 表示式來寫。

函式式介面舉例:

@FunctionalInterface // 介面上新增 @FunctionalInterface 註解
public interface Runnable {
    /**
     * When an object implementing interface {@code Runnable} is used
     * to create a thread, starting the thread causes the object's
     * {@code run} method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method {@code run} is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run(); // 介面中只有一個抽象方法
}

自定義函式式介面:

  • 函式式介面中不使用泛型:

    @FunctionalInterface
    public interface MyNumber {
        double getValue();
    }
    
  • 函式式介面中使用泛型:

    @FunctionalInterface
    public interface MyNumber<T> {
        T getValue(T t);
    }
    

作為引數傳遞 Lambda 表示式

image-20210410174532960

Java 內建四大核心函式式介面:

image-20210410193809887

其他介面:

image-20210410193935197

示例:

/**
 * Java 內建的 4 大核心函式式介面:
 *
 * 消費型介面 Consumer<T>     void accept(T t)
 * 供給型介面 Supplier<T>     T get()
 * 函式型介面 Function<T,R>   R apply(T t)
 * 斷定型介面 Predicate<T>    boolean test(T t)
 */
public class LambdaTest {
    // 作為引數傳遞 Lambda 表示式
    // happyTime():將引數 1 傳給函式式介面 con,Consumer 函式式介面包含唯一方法 accept()
    public void happyTime(double money, Consumer<Double> con) {
        con.accept(money);
    }

    @Test
    public void test1() {
        happyTime(500, new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {// 重寫 accept()
                System.out.println("學習太累了,去天上人間買了瓶礦泉水,價格為:" + aDouble);
            }
        });

        System.out.println("********************");

        happyTime(400, money -> System.out.println("學習太累了,去天上人間喝了口水,價格為:" + money));
    }

    // filterString():根據給定的規則,過濾集合中的字串。此規則由 Predicate 的方法決定
    // Predicate 函式式介面包含唯一方法 test()
    public List<String> filterString(List<String> list, Predicate<String> pre) {
        ArrayList<String> filterList = new ArrayList<>();
        // 過濾 list 中的每一個元素,透過 Predicate 例項 test() 驗證的,新增到 filterList 中並返回
        for (String s : list) {
            if (pre.test(s)) {
                filterList.add(s);
            }
        }
        return filterList;
    }

    @Test
    public void test2() {
        List<String> list = Arrays.asList("北京", "南京", "天津", "東京", "西京", "普京");

        List<String> filterStrs = filterString(list, new Predicate<String>() {
            @Override
            public boolean test(String s) {// 重寫 test()
                return s.contains("京");
            }
        });
        System.out.println(filterStrs);

        System.out.println("********************");

        List<String> filterStrs1 = filterString(list, s -> s.contains("京"));
        System.out.println(filterStrs1);
    }
}

關於面向函式程式設計的精髓,可以從以下連結中體會:https://blog.csdn.net/qq_27416233/article/details/83418791

方法引用

當要傳遞給 Lambda 體的操作,已經有實現的方法了,可以使用方法引用(Method References)

方法引用可以看做是 Lambda 表示式深層次的表達。換句話說,方法引用就是 Lambda 表示式,也就是函式式介面的一個例項,透過方法的名字來指向一個方法,可以認為是 Lambda 表示式的一個語法糖

格式:使用運算子::將類(或物件)與方法名分隔開來。

方法引用有如下三種主要使用情況:

  • 物件::例項方法名
  • 類::靜態方法名
  • 類::例項方法

要求:

  • 針對情況一和情況二:實現介面的抽象方法的引數列表和返回值型別,必須與方法引用的方法的引數列表和返回值型別保持一致!
  • 針對情況三:ClassName::methodName,當函式式介面方法的第一個引數是方法引用的方法的呼叫者,並且第二個引數是方法引用的方法的引數(或無引數/返回值型別)時使用。

示例:

public class Employee {

    private int id;
    private String name;
    private int age;
    private double salary;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Employee() {
        System.out.println("Employee().....");
    }

    public Employee(int id) {
        this.id = id;
        System.out.println("Employee(int id).....");
    }

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public Employee(int id, String name, int age, double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        Employee employee = (Employee) o;

        if (id != employee.id)
            return false;
        if (age != employee.age)
            return false;
        if (Double.compare(employee.salary, salary) != 0)
            return false;
        return name != null ? name.equals(employee.name) : employee.name == null;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + age;
        temp = Double.doubleToLongBits(salary);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
    }
}
/**
 * 方法引用的使用
 *
 * 1. 使用情境:當要傳遞給 Lambda 體的操作,已經有實現的方法了,可以使用方法引用!
 *
 * 2. 方法引用,本質上就是 Lambda 表示式,而 Lambda 表示式作為函式式介面的例項。所以方法引用,也是函式式介面的例項。
 *
 * 3. 使用格式:  類(或物件)::方法名
 *
 * 4. 具體分為如下的三種情況:
 *    情況 1     物件::非靜態方法
 *    情況 2     類::靜態方法
 *
 *    情況 3     類::非靜態方法
 *
 * 5. 方法引用使用的要求:要求介面中的抽象方法的形參列表和返回值型別與方法引用的方法的形參列表和返回值型別相同!(針對於情況 1 和情況 2)
 */
public class MethodRefTest {
    // 情況一:物件::例項方法
    // Consumer 中的 void accept(T t)
    // PrintStream 中的 void println(T t)
    @Test
    public void test1() {
        // System.out.println(str) 這個方法體,在 PrintStream 中已經存在實現的方法
        Consumer<String> con1 = str -> System.out.println(str);
        con1.accept("北京");

        System.out.println("*******************");

        PrintStream ps = System.out;// 利用 System.out 的物件,呼叫其 println() 方法
        Consumer<String> con2 = ps::println;
        con2.accept("beijing");
    }

    // Supplier 中的 T get()
    // Employee 中的 String getName()
    @Test
    public void test2() {
        Employee emp = new Employee(1001, "Tom", 23, 5600);

        // emp.getName() 這個方法體,對應的就是 emp 物件的 getName() 方法
        Supplier<String> sup1 = () -> emp.getName();
        System.out.println(sup1.get());// 返回 emp 物件的 name

        System.out.println("*******************");

        Supplier<String> sup2 = emp::getName;
        System.out.println(sup2.get());
    }

    // 情況二:類::靜態方法
    // Comparator 中的 int compare(T t1,T t2)
    // Integer 中的 int compare(T t1,T t2)
    @Test
    public void test3() {
        Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);
        System.out.println(com1.compare(12, 21));

        System.out.println("*******************");

        Comparator<Integer> com2 = Integer::compare;
        System.out.println(com2.compare(12, 3));
    }

    // Function 中的 R apply(T t)
    // Math 中的 Long round(Double d)
    @Test
    public void test4() {
        Function<Double, Long> func = new Function<Double, Long>() {
            @Override
            public Long apply(Double d) {
                return Math.round(d);
            }
        };

        System.out.println("*******************");

        Function<Double, Long> func1 = d -> Math.round(d);// lambda 表示式
        System.out.println(func1.apply(12.3));

        System.out.println("*******************");

        Function<Double, Long> func2 = Math::round;// 方法引用
        System.out.println(func2.apply(12.6));
    }

    // 情況三:類::例項方法
    // Comparator 中的 int comapre(T t1,T t2)
    // String 中的 int t1.compareTo(t2)
    @Test
    public void test5() {
        Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
        System.out.println(com1.compare("abc", "abd"));

        System.out.println("*******************");

        Comparator<String> com2 = String::compareTo;
        System.out.println(com2.compare("abd", "abm"));
    }

    // BiPredicate 中的 boolean test(T t1, T t2);
    // String 中的 boolean t1.equals(t2)
    @Test
    public void test6() {
        // 原始寫法
        BiPredicate<String, String> pre = new BiPredicate<String, String>() {
            @Override
            public boolean test(String s1, String s2) {
                return s1.equals(s2);
            }
        };
        System.out.println(pre.test("abc", "abc"));

        System.out.println("*******************");

        // lambda 表示式:lambda 體是引數 1 呼叫一個方法,引數 2 是那個方法的入參
        BiPredicate<String, String> pre1 = (s1, s2) -> s1.equals(s2);
        System.out.println(pre1.test("abc", "abc"));

        System.out.println("*******************");

        // 方法引用:String 類的 equals() 符合上述 lambda 體的功能
        BiPredicate<String, String> pre2 = String::equals;
        System.out.println(pre2.test("abc", "abd"));
    }

    // Function 中的 R apply(T t)
    // Employee 中的 String getName();
    @Test
    public void test7() {
        Employee employee = new Employee(1001, "Jerry", 23, 6000);

        // 原始寫法:lambda 體是引數 1 呼叫一個方法,返回一個引數 2 型別的值
        Function<Employee, String> func = new Function<Employee, String>() {
            @Override
            public String apply(Employee employee) {
                return employee.getName();
            }
        };

        System.out.println("*******************");

        // lambda 表示式:Employee 類的 getName() 符合上述 lambda 體的功能
        Function<Employee, String> func1 = e -> e.getName();
        System.out.println(func1.apply(employee));

        System.out.println("*******************");

        // 方法引用
        Function<Employee, String> func2 = Employee::getName;
        System.out.println(func2.apply(employee));
    }
}

構造器引用

格式:ClassName::new

與函式式介面相結合,自動與函式式介面中方法相容。可以把構造器引用賦值給定義的方法,要求構造器引數列表要與介面中抽象方法的引數列表一致,且方法的返回值即為構造器對應類的物件。

示例:

/**
 * 一、構造器引用
 *      和方法引用類似,函式式介面的抽象方法的形參列表和構造器的形參列表一致。
 *      抽象方法的返回值型別即為構造器所屬的類的型別
 */
public class ConstructorRefTest {
    // 構造器引用
    // Supplier 中的 T get()
    // Employee 的空參構造器:Employee()
    @Test
    public void test1() {
        // 原始寫法
        Supplier<Employee> sup = new Supplier<Employee>() {
            @Override
            public Employee get() {
                return new Employee();
            }
        };
        System.out.println(sup.get());

        System.out.println("*******************");

        // Lambda 表示式
        Supplier<Employee> sup1 = () -> new Employee();
        System.out.println(sup1.get());

        System.out.println("*******************");

        // 方法引用:Employee 的無參構造器符合上述 Lambda 體
        Supplier<Employee> sup2 = Employee::new;
        System.out.println(sup2.get());
    }

    // Function 中的 R apply(T t)
    @Test
    public void test2() {
        // 原始寫法
        Function<Integer, Employee> func = new Function<Integer, Employee>() {
            @Override
            public Employee apply(Integer id) {
                return new Employee(id);
            }
        };
        Employee employee = func.apply(1000);
        System.out.println(employee);

        System.out.println("*******************");

        // Lambda 表示式
        Function<Integer, Employee> func1 = id -> new Employee(id);
        Employee employee1 = func1.apply(1001);
        System.out.println(employee1);

        System.out.println("*******************");

        // 方法引用:Employee 的帶 id 的有參構造器符合上述 Lambda 體
        Function<Integer, Employee> func2 = Employee::new;
        Employee employee2 = func2.apply(1002);
        System.out.println(employee2);
    }

    // BiFunction 中的 R apply(T t,U u)
    @Test
    public void test3() {
        // 原始寫法
        BiFunction<Integer, String, Employee> func = new BiFunction<Integer, String, Employee>() {
            @Override
            public Employee apply(Integer id, String name) {
                return new Employee(id, name);
            }
        };
        System.out.println(func.apply(1000, "Tom"));

        System.out.println("*******************");

        // Lambda 表示式
        BiFunction<Integer, String, Employee> func1 = (id, name) -> new Employee(id, name);
        System.out.println(func1.apply(1001, "Tom"));

        System.out.println("*******************");

        // 方法引用:Employee 的帶 id 和 name 的有參構造器符合上述 Lambda 體
        BiFunction<Integer, String, Employee> func2 = Employee::new;
        System.out.println(func2.apply(1002, "Tom"));
    }
}

陣列引用

格式:type[]::new

可以把陣列看做是一個特殊的類,則寫法與構造器引用一致。

示例:

/**
 * 二、陣列引用
 *     大家可以把陣列看做是一個特殊的類,則寫法與構造器引用一致。
 */
public class ConstructorRefTest {
    // 陣列引用
    // Function 中的 R apply(T t)
    @Test
    public void test4() {
        // 原始寫法
        Function<Integer, String[]> func = new Function<Integer, String[]>() {
            @Override
            public String[] apply(Integer length) {
                return new String[length];
            }
        };
        String[] arr = func.apply(1);
        System.out.println(Arrays.toString(arr));

        System.out.println("*******************");

        // Lambda 表示式
        Function<Integer, String[]> func1 = length -> new String[length];
        String[] arr1 = func1.apply(5);
        System.out.println(Arrays.toString(arr1));

        System.out.println("*******************");

        // 方法引用
        Function<Integer, String[]> func2 = String[]::new;
        String[] arr2 = func2.apply(10);
        System.out.println(Arrays.toString(arr2));
    }
}

Optional 類

到目前為止,臭名昭著的空指標異常是導致 Java 應用程式失敗的最常見原因。以前,為了解決空指標異常,Google 公司著名的 Guava 專案引入了 Optional 類,Guava 透過使用檢查空值的方式來防止程式碼汙染,它鼓勵程式設計師寫更乾淨的程式碼。受到 Google Guava 的啟發,Optional 類已經成為 Java 8 類庫的一部分。

Optional<T>類(java.util.Optional)是一個容器類,它可以儲存型別 T 的值,代表這個值存在。或者僅僅儲存 null,表示這個值不存在。原來用 null 表示一個值不存在,現在 Optional 可以更好的表達這個概念。並且可以避免空指標異常。

  • Optional 類的 Javadoc 描述如下:這是一個可以為 null 的容器物件。如果值存在則isPresent()會返回 true,呼叫get()會返回該物件。

  • Optional 類提供了很多有用的方法,這樣我們就不用顯式進行空值檢測。

建立 Optional 類物件的方法:

  • Optional.of(T t):建立一個 Optional 例項,t 必須非空。否則,報 NullPointerException。

    public class OptionalTest {
        @Test
        public void test() {
            Optional<Employee> opt = Optional.of(new Employee("張三", 8888));
            // 判斷 opt 中員工物件是否滿足條件,如果滿足就保留,否則返回空
            Optional<Employee> emp = opt.filter(e -> e.getSalary() > 10000);
            System.out.println(emp);
        }
    }
    
    public class OptionalTest {
        @Test
        public void test() {
            Optional<Employee> opt = Optional.of(new Employee("張三", 8888));
            // 如果 opt 中員工物件不為空,就漲薪 10%
            Optional<Employee> emp = opt.map(e ->
            {
                e.setSalary(e.getSalary() % 1.1);
                return e;
            });
            System.out.println(emp);
        }
    }
    
  • Optional.empty():建立一個空的 Optional 例項。

  • Optional.ofNullable(T t):建立一個 Optional 例項,t 可以為 null。

判斷 Optional 容器中是否包含物件:

  • boolean isPresent():判斷是否包含物件。

  • void ifPresent(Consumer<? super T> consumer):如果有值,就執行 Consumer 介面的實現程式碼,並且該值會作為引數傳給它。

    public class OptionalTest {
        @Test
        public void test() {
            Boy b = new Boy("張三");
            Optional<Girl> opt = Optional.ofNullable(b.getGrilFriend());
            // 如果女朋友存在就列印女朋友的資訊
            opt.ifPresent(System.out::println);
        }
    }
    

獲取 Optional 容器的物件:

  • T get():如果呼叫物件包含值,返回該值,否則拋異常。可以對應於Optional.of(T t)一起使用。

  • T orElse(T other):如果有值則將其返回,否則返回指定的 other 物件。可以對應於Optional.ofNullable(T t)一起使用。

    public class OptionalTest {
        @Test
        public void test() {
            Boy b = new Boy("張三");
            Optional<Girl> opt = Optional.ofNullable(b.getGrilFriend());
            // 如果有女朋友就返回他的女朋友,否則只能欣賞 "嫦娥" 了
            Girl girl = opt.orElse(new Girl("嫦娥"));
            System.out.println("他的女朋友是:" + girl.getName());
        }
    }
    
  • T orElseGet(Supplier<? extends T> other):如果有值則將其返回,否則返回由 Supplier 介面實現提供的物件。

  • T orElseThrow(Supplier<? extends X> exceptionSupplier):如果有值則將其返回,否則丟擲由 Supplier 介面實現提供的異常。

示例:

public class Boy {
    private Girl girl;

    public Girl getGirl() {
        return girl;
    }

    public void setGirl(Girl girl) {
        this.girl = girl;
    }

    public Boy() {
    }

    public Boy(Girl girl) {
        this.girl = girl;
    }

    @Override
    public String toString() {
        return "Boy{" +
                "girl=" + girl +
                '}';
    }
}
public class Girl {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Girl() {
    }

    public Girl(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Girl{" +
                "name='" + name + '\'' +
                '}';
    }
}
/**
 * Optional 類:為了在程式中避免出現空指標異常而建立的。
 *
 * 常用的方法:ofNullable(T t)
 *           orElse(T t)
 */
public class OptionalTest {
    /*
    Optional.of(T t):建立一個 Optional 例項,t 必須非空。否則,報 NullPointerException
    Optional.empty():建立一個空的 Optional 例項
    Optional.ofNullable(T t):t 可以為 null
     */
    @Test
    public void test1() {
        Girl girl = new Girl();
        // girl = null;

        // of(T t):保證 t 是非空的
        Optional<Girl> optionalGirl = Optional.of(girl);
    }

    @Test
    public void test2() {
        Girl girl = new Girl();
        // girl = null;

        // ofNullable(T t):t 可以為 null
        Optional<Girl> optionalGirl = Optional.ofNullable(girl);
        System.out.println(optionalGirl);

        // orElse(T t1):如果當前的 Optional 內部封裝的 t 是非空的,則返回內部的 t。
        //                  如果內部的 t 是空的,則返回 orElse() 方法中的引數 t1。
        Girl girl1 = optionalGirl.orElse(new Girl("趙"));
        System.out.println(girl1);
    }

    @Test
    public void test3() {
        Boy boy = new Boy();
        boy = null;
        String girlName = getGirlName(boy);
        // String girlName = getGirlName1(boy);// 不會出現 NullPointerException
        System.out.println(girlName);
    }

    @Test
    public void test4() {
        Boy boy = null;
        boy = new Boy();
        boy = new Boy(new Girl("蒼"));
        String girlName = getGirlName2(boy);
        System.out.println(girlName);
    }


    // 未最佳化程式碼,容易出現 NullPointerException
    public String getGirlName(Boy boy) {
        return boy.getGirl().getName();
    }

    // 最佳化以後的 getGirlName()
    public String getGirlName1(Boy boy) {
        if (boy != null) {
            Girl girl = boy.getGirl();
            if (girl != null) {
                return girl.getName();
            }
        }
        return null;
    }

    // 使用 Optional 類最佳化的 getGirlName()
    public String getGirlName2(Boy boy) {
        // boy 可能為空
        Optional<Boy> boyOptional = Optional.ofNullable(boy);
        // 此時的 boy1 一定非空
        Boy boy1 = boyOptional.orElse(new Boy(new Girl("迪")));

        // girl 可能為空
        Girl girl = boy1.getGirl();
        Optional<Girl> girlOptional = Optional.ofNullable(girl);
        // 此時的 girl1 一定非空
        Girl girl1 = girlOptional.orElse(new Girl("古"));
        return girl1.getName();
    }
}

Stream API

Java 8 中有兩大最為重要的改變。第一個是Lambda 表示式;另外一個則是Stream API

Stream API(java.util.stream)把真正的函數語言程式設計風格引入到 Java 中。這是目前為止對 Java 類庫最好的補充,因為 Stream API 可以極大提供 Java 程式設計師的生產力,讓程式設計師寫出高效率、乾淨、簡潔的程式碼。

Stream 是 Java 8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查詢、過濾和對映資料等操作。 使用 Stream API 對集合資料進行操作,就類似於使用 SQL 執行的資料庫查詢。也可以使用 Stream API 來並行執行操作。簡言之,Stream API 提供了一種高效且易於使用的處理資料的方式。

為什麼要使用 Stream API:

  • 實際開發中,專案中多數資料來源都來自於 MySQL,Oracle 等。但現在資料來源可以更多了,有 MongDB,Redis 等,而這些 NoSQL 的資料就需要 Java 層面去處理。
  • Stream 和 Collection 集合的區別:Collection 是一種靜態的記憶體資料結構,而 Stream 是有關計算的。Collection 主要面向記憶體,儲存在記憶體中,Stream 主要面向 CPU,透過 CPU 實現計算。

Stream 就是一個資料渠道,用於運算元據源(集合、陣列等)所生成的元素序列。"集合講的是資料,Stream 講的是計算!"

Stream 的特性:

  • Stream 自己不會儲存元素。
  • Stream 不會改變源物件。相反,它們會返回一個持有結果的新 Stream。
  • Stream 操作是延遲執行的。這意味著它們會等到需要結果的時候才執行。

Stream 操作的三個步驟:

image-20210411143020376

  • 1 - 建立 Stream
    • 一個資料來源(如:集合、陣列),獲取一個流。
  • 2 - 中間操作
    • 一箇中間操作鏈,對資料來源的資料進行處理。
  • 3 - 終止操作(操作)
    • 一旦執行終止操作,就執行中間操作鏈,併產生結果。之後,不會再被使用。

Stream 的建立

步驟一:Stream 的四種建立方式。

  • 方式一:透過集合

    • Java 8 中的 Collection 介面被擴充套件,提供了兩個獲取流的方法:
      • default Stream<E> stream():返回一個順序流。
      • default Stream<E> parallelStream():返回一個並行流。
  • 方式二:透過陣列

    • Java 8 中的 Arrays 類的靜態方法stream()可以獲取陣列流:
      • static <T> Stream<T> stream(T[] array):返回一個特殊物件陣列的流。
    • 過載形式,能夠處理對應基本型別的陣列:
      • public static IntStream stream(int[] array):返回一個 int 陣列的流。
      • public static LongStream stream(long[] array):返回一個 long 陣列的流。
      • public static DoubleStream stream(double[] array):返回一個 double 陣列的流。
  • 方式三:透過 Stream 類的of()

    • 可以呼叫 Stream 類靜態方法of(),透過顯示值建立一個流。它可以接收任意數量的引數。
      • public static<T> Stream<T> of(T... values):返回一個流。
  • 方式四:建立無限流

    • 可以使用靜態方法Stream.iterate()Stream.generate()這兩種方式,建立無限流。
      • 迭代:public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
      • 生成:public static<T> Stream<T> generate(Supplier<T> s)

示例:

/**
 * 提供用於測試的資料
 */
public class EmployeeData {
    public static List<Employee> getEmployees() {
        List<Employee> list = new ArrayList<>();
        list.add(new Employee(1001, "馬1", 34, 6000.38));
        list.add(new Employee(1002, "馬2", 12, 9876.12));
        list.add(new Employee(1003, "劉", 33, 3000.82));
        list.add(new Employee(1004, "雷", 26, 7657.37));
        list.add(new Employee(1005, "李", 65, 5555.32));
        list.add(new Employee(1006, "比", 42, 9500.43));
        list.add(new Employee(1007, "任", 26, 4333.32));
        return list;
    }
}
/**
 * 1. Stream 關注的是對資料的運算,與 CPU 打交道
 *    集合關注的是資料的儲存,與記憶體打交道
 *
 * 2.
 * 	① Stream 自己不會儲存元素
 * 	② Stream 不會改變源物件。相反,它們會返回一個持有結果的新 Stream
 * 	③ Stream 操作是延遲執行的。這意味著它們會等到需要結果的時候才執行
 *
 * 3. Stream 執行流程
 * 	① Stream 的例項化
 * 	② 一系列的中間操作(過濾、對映、...)
 * 	③ 終止操作
 *
 * 4. 說明:
 * 4.1 一箇中間操作鏈,對資料來源的資料進行處理
 * 4.2 一旦執行終止操作,就執行中間操作鏈,併產生結果。之後,不會再被使用
 *
 */
public class StreamAPITest {
    // 建立 Stream 方式一:透過集合
    @Test
    public void test1() {
        List<Employee> employees = EmployeeData.getEmployees();

        // 方法一:
        // default Stream<E> stream():返回一個順序流
        Stream<Employee> stream = employees.stream();

        // 方法二:
        // default Stream<E> parallelStream():返回一個並行流
        Stream<Employee> parallelStream = employees.parallelStream();
    }

    // 建立 Stream 方式二:透過陣列
    @Test
    public void test2() {
        int[] arr = new int[]{1, 2, 3, 4, 5, 6};

        // 呼叫 Arrays 類的 static <T> Stream<T> stream(T[] array):返回一個流
        IntStream stream = Arrays.stream(arr);

        Employee e1 = new Employee(1001, "Tom");
        Employee e2 = new Employee(1002, "Jerry");
        Employee[] arr1 = new Employee[]{e1, e2};
        Stream<Employee> stream1 = Arrays.stream(arr1);
    }

    // 建立 Stream 方式三:透過 Stream 的 of()
    @Test
    public void test3() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);

        Stream<String> stringStream = Stream.of("A", "B", "C", "D", "E", "F");
    }

    // 建立 Stream 方式四:建立無限流 --- 用的比較少
    @Test
    public void test4() {
        // 迭代
        // public static<T > Stream < T > iterate( final T seed, final UnaryOperator<T> f)
        // 遍歷前 10 個偶數
        Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);// 從 0 開始,後一個數是前一個數 +2

        // 生成
        // public static<T> Stream<T> generate(Supplier<T> s)
        // 遍歷前 10 個隨機數
        Stream.generate(Math::random).limit(10).forEach(System.out::println);
    }
}

Stream 的中間操作

步驟二:Stream 的中間操作。

  • 多箇中間操作可以連線起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理!而在終止操作時一次性全部處理,這稱為惰性求值

篩選與切片

方法 描述
filter(Predicate<? super T> predicate) 過濾,接收 Lambda,從流中排除某些元素
distinct() 去重,根據流所生成元素的 hashCode() 和 equals() 去除重複元素
limit(long maxSize) 截斷流,使其元素不超過給定數量 maxSize
skip(long n) 跳過元素,返回一個扔掉了前 n 個元素的流,若流中的元素不足 n 個,則返回一個空流,與 limit(n) 互補

示例:

public class StreamAPITest {
    // 1 - 篩選與切片
    @Test
    public void test1() {
        List<Employee> list = EmployeeData.getEmployees();

        // filter(Predicate p) --- 接收Lambda,從流中排除某些元素。
        // 練習:查詢員工表中薪資大於7000的員工資訊
        list.stream().filter(e -> e.getSalary() > 7000).forEach(System.out::println);

        System.out.println("************************");

        // limit(n) --- 截斷流,使其元素不超過給定數量n。
        // 練習:列印員工表中前三名的員工資訊
        list.stream().limit(3).forEach(System.out::println);// 前一個流已經關閉,必須重新建一個流

        System.out.println("************************");

        // skip(n) --- 跳過元素,返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空流。與limit(n)互補。
        // 練習:跳過員工表中前三名的員工資訊,然後列印之後的每個員工的資訊
        list.stream().skip(3).forEach(System.out::println);

        System.out.println("************************");

        // distinct() --- 篩選,透過流所生成元素的hashCode()和equals()去除重複元素
        list.add(new Employee(1010, "劉強東", 40, 8000));
        list.add(new Employee(1010, "劉強東", 41, 8000));
        list.add(new Employee(1010, "劉強東", 40, 8000));
        list.add(new Employee(1010, "劉強東", 40, 8000));
        list.add(new Employee(1010, "劉強東", 40, 8000));
        // System.out.println(list);
        list.stream().distinct().forEach(System.out::println);
    }
}

對映

方法 描述
map(Function<? super T, ? extends R> mapper) 接收一個函式作為引數,該函式會被應用到流中每個元素上,並將其對映成一個新的元素
mapToInt(ToIntFunction<? super T> mapper) 接收一個函式作為引數,該函式會被應用到流中每個元素上,並將其對映成一個新的 IntStream
mapToLong(ToLongFunction<? super T> mapper) 接收一個函式作為引數,該函式會被應用到流中每個元素上,並將其對映成一個新的 LongStream
mapToDouble(ToDoubleFunction<? super T> mapper) 接收一個函式作為引數,該函式會被應用到流中每個元素上,並將其對映成一個新的 DoubleSteam
flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) 接收一個函式作為引數,將流中的每個元素都換成另一個流,然後把所有流連線成一個新的流
flatMapToInt(Function<? super T, ? extends IntStream> mapper) 接收一個函式作為引數,將流中的每個元素都換成另一個流,然後把所有流連線成一個新的 IntStream
flatMapToLong(Function<? super T, ? extends LongStream> mapper) 接收一個函式作為引數,將流中的每個元素都換成另一個流,然後把所有流連線成一個新的 LongStream
flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper) 接收一個函式作為引數,將流中的每個元素都換成另一個流,然後把所有流連線成一個新的 DoubleSteam

map() VS flatMap():

  • map() 的主要作用是將流中的每個元素按照給定的函式進行轉換,然後返回一個包含轉換後元素的新流。這個方法不會改變原始流的元素數量,只是對每個元素進行一對一的轉換。
  • flatMap() 用於將流中的每個元素轉換為一個流,然後將這些流扁平化(即將多個子流中的元素合併到一個流中),這個方法在處理包含多個子元素的流(如包含集合的集合的流)時非常有用。

示例:

public class StreamAPITest {
    // 將字串中的多個字元構成的集合轉換為對應的 Stream 的例項
    public static Stream<Character> fromStringToStream(String str) {// 如:aa ---> 返回兩個字元 a 組成的集合對應的流
        ArrayList<Character> list = new ArrayList<>();
        for (Character c : str.toCharArray()) {
            list.add(c);
        }
        return list.stream();
    }
    
    // 2-對映
    @Test
    public void test2() {
        // map(Function f) --- 接收一個函式作為引數,將元素轉換成其他形式或提取資訊,該函式會被應用到每個元素上,並將其對映成一個新的元素
        //      ---> 類似於 List 的 add():如果流的每個值轉換成新流,則將每個新流作為一個元素組成新的流
        //            即類似:[1, [1, 2], 5, [1, 3, 2, 5], 9]

        // 練習 1:將 list 中的每一個元素變成大寫並列印
        List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
        // list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
        list.stream().map(String::toUpperCase).forEach(System.out::println);

        System.out.println();

        // 練習 2:獲取員工姓名長度大於 3 的員工的姓名
        List<Employee> employees = EmployeeData.getEmployees();
        Stream<String> namesStream = employees.stream().map(Employee::getName);
        namesStream.filter(name -> name.length() > 3).forEach(System.out::println);

        System.out.println();

        //  練習3:
        Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest::fromStringToStream);
        // streamStream.forEach(System.out::println);
        // 體會下下面的寫法與上面寫法的區別
        streamStream.forEach(s -> {
            s.forEach(System.out::println);
        });

        System.out.println("************************");

        // flatMap(Function f) --- 接收一個函式作為引數,將流中的每個值都換成另一個流,然後把所有流連線成一個流
        //      ---> 類似於 List 的 addAll():如果流的每個值轉換成新流,則將每個新流的值組合連線成一個流
        //            即類似:[1, 1, 2, 5, 1, 3, 2, 5, 9]
        Stream<Character> characterStream = list.stream().flatMap(StreamAPITest::fromStringToStream);
        characterStream.forEach(System.out::println);
    }

    // 對比 map() 和 flatmap() 的區別
    @Test
    public void test3() {
        ArrayList list1 = new ArrayList();
        list1.add(1);
        list1.add(2);
        list1.add(3);

        ArrayList list2 = new ArrayList();
        list2.add(4);
        list2.add(5);
        list2.add(6);

        list1.add(list2);// [1, 2, 3, [4, 5, 6]]
        list1.addAll(list2);// [1, 2, 3, 4, 5, 6]
        System.out.println(list1);
    }
}

排序

方法 描述
sorted() 產生一個新流,元素按自然排序
sorted(Comparator<? super T> comparator) 產生一個新流,元素按定製排序

示例:

public class StreamAPITest {
    // 3-排序
    @Test
    public void test4() {
        // sorted() --- 自然排序
        List<Integer> list = Arrays.asList(12, 43, 65, 34, 87, 0, -98, 7);
        list.stream().sorted().forEach(System.out::println);
        // 拋異常,原因:Employee 沒有實現 Comparable 介面
        // List<Employee> employees = EmployeeData.getEmployees();
        // employees.stream().sorted().forEach(System.out::println);

        // sorted(Comparator com) --- 定製排序
        List<Employee> employees = EmployeeData.getEmployees();
        employees.stream().sorted((e1, e2) -> {
            int ageValue = Integer.compare(e1.getAge(), e2.getAge());// 先按年齡
            if (ageValue != 0) {
                return ageValue;
            } else {
                return -Double.compare(e1.getSalary(), e2.getSalary());// 再按薪水
            }
        }).forEach(System.out::println);
    }
}

Stream 的終止操作

步驟三:Stream 的終止操作。

終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、Integer,甚至是 void。

流進行了終止操作後,不能再次使用。

匹配與查詢

方法 描述
anyMatch(Predicate<? super T> predicate) 檢查是否至少匹配流中的一個元素
allMatch(Predicate<? super T> predicate) 檢查是否至少匹配流中的所有元素
noneMatch(Predicate<? super T> predicate) 檢查是否沒有匹配流中的所有元素
findFirst() 返回流中的第一個元素
findAny() 返回流中的任意一個元素
min(Comparator<? super T> comparator) 返回流中的最小值
max(Comparator<? super T> comparator) 返回流中的最大值
count() 返回流中元素的總數
forEach(Consumer<? super T> action) 內部迭代(使用 Collection 介面需要使用者去做迭代,這叫做外部迭代)

示例:

public class StreamAPITest {
    // 1-匹配與查詢
    @Test
    public void test1() {
        List<Employee> employees = EmployeeData.getEmployees();

        // allMatch(Predicate p) --- 檢查是否匹配所有元素
        // 練習:是否所有的員工的年齡都大於 18
        boolean allMatch = employees.stream().allMatch(e -> e.getAge() > 18);
        System.out.println(allMatch);

        // anyMatch(Predicate p) --- 檢查是否至少匹配一個元素
        // 練習:是否存在員工的工資大於 10000
        boolean anyMatch = employees.stream().anyMatch(e -> e.getSalary() > 10000);
        System.out.println(anyMatch);

        // noneMatch(Predicate p) ---- 檢查是否沒有匹配的元素。如果有,返回 false
        // 練習:是否存在員工姓 "雷"
        boolean noneMatch = employees.stream().noneMatch(e -> e.getName().startsWith("雷"));
        System.out.println(noneMatch);

        // findFirst() --- 返回第一個元素
        Optional<Employee> employee = employees.stream().findFirst();
        System.out.println(employee);

        // findAny() --- 返回當前流中的任意元素
        Optional<Employee> employee1 = employees.parallelStream().findAny();
        System.out.println(employee1);
    }

    @Test
    public void test2() {
        List<Employee> employees = EmployeeData.getEmployees();
        // count --- 返回流中元素的總個數
        // 練習:返回工資高於 5000 的員工個數
        long count = employees.stream().filter(e -> e.getSalary() > 5000).count();
        System.out.println(count);

        // max(Comparator c) --- 返回流中最大值
        // 練習:返回最高的工資
        Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
        Optional<Double> maxSalary = salaryStream.max(Double::compare);
        System.out.println(maxSalary);

        // min(Comparator c) --- 返回流中最小值
        // 練習:返回最低工資的員工
        Optional<Employee> employee = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
        System.out.println(employee);

        System.out.println("************************");

        // forEach(Consumer c) --- 內部迭代
        employees.stream().forEach(System.out::println);
        // 外部迭代
        Iterator<Employee> iterator = employees.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        // 使用集合的遍歷操作方法
        employees.forEach(System.out::println);
    }
}

歸約

方法 描述
reduce(T identity, BinaryOperator accumulator) 將流中的元素反覆結合起來,得到一個值,返回 T
reduce(BinaryOperator<T> accumulator) 將流中的元素反覆結合起來,得到一個值,返回 Optional
  • map 和 reduce 的連線通常稱為map-reduce模式,因 Google 用它來進行網路搜尋而出名。
  • map 是一對一對映,由 n 到 n;reduce 是多對一歸約,由 n 到 1。

示例:

public class StreamAPITest {
    // 2-歸約
    @Test
    public void test3() {
        // reduce(T identity, BinaryOperator) --- 可以將流中元素反覆結合起來,得到一個值,返回 T
        // 練習 1:計算1-10的自然數的和
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Integer sum = list.stream().reduce(0, Integer::sum);// 有一個初始值,在初始值基礎上操作
        System.out.println(sum);

        // reduce(BinaryOperator) --- 可以將流中元素反覆結合起來,得到一個值,返回 Optional<T>
        // 練習 2:計算公司所有員工工資的總和
        List<Employee> employees = EmployeeData.getEmployees();
        Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
        Optional<Double> sumMoney = salaryStream.reduce((d1, d2) -> d1 + d2);
        // Optional<Double> sumMoney = salaryStream.reduce(Double::sum);// 方法引用
        // Double sumMoney = salaryStream.reduce(0.0, Double::sum);// 也可以計算工資總和
        System.out.println(sumMoney.get());
    }
}

收集

方法 描述
collect(Collector<? super T, A, R> collector) 將流轉換為其他形式,接收一個 Collector 介面的實現,用於給 Stream 中元素做彙總的方法
  • Collector 介面中方法的實現決定了如何對流執行收集的操作,如收集到 List、Set、Map 等。

  • Collectors 實用類提供了很多靜態方法,可以方便地建立常見收集器例項(Collector 例項),具體方法與例項如下表:

    方法 返回型別 描述 示例
    toList() List 把流中的元素收集到 List List emps= list.stream().collect(Collectors.toList());
    toSet() Set 把流中的元素收集到 Set Set emps= list.stream().collect(Collectors.toSet());
    toCollection(Supplier<C> collectionFactory) Collection 把流中的元素收集到建立的集合 Collection emps= list.stream().collect(Collectors.toCollection(ArrayList::new));
    counting() Long 計算流中元素的個數 list.stream().collect(Collectors.counting());
    summingInt(ToIntFunction<? super T> mapper) Integer 對流中元素的整數屬性求和 list.stream().collect(Collectors.summingInt(Employee::getSalary));
    averagingInt(ToIntFunction<? super T> mapper) Double 計算流中元素整數屬性的平均值 list.stream().collect(Collectors.averagingInt(Employee::getSalary));
    summarizingInt(ToIntFunction<? super T> mapper) IntSummaryStatistics 收集流中整數屬性的統計值,如平均值 list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
    joining() String 連線流中的每個字串 list.stream().map(Employee::getName).collect(Collectors.joining());
    minBy(Comparator<? super T> comparator) Optional 根據比較器選擇最小值 list.stream().collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary)));
    maxBy(Comparator<? super T> comparator) Optional 根據比較器選擇最大值 list.stream().collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)));
    reducing(BinaryOperator op) 歸約產生的型別 從一個作為累加器的初始值開始,利用 BinaryOperator 與流中元素逐個結合,從而規約成單個值 list.stream().collect(Collectors.reducing(0, Employee::getSalary, Integer::sum));
    collectingAndThen(Collector<T,A,R> downstream, Function<R,RR> finisher) 轉換函式返回的型別 包裹另一個收集器,對其結果轉換函式 list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
    groupingBy(Function<? super T, ? extends K> classifier) Map<K, List> 根據某屬性值對流分組,屬性為 K,結果為 V list.stream().collect(Collectors.groupingBy(Employee::getDepartmentName));
    partitioningBy(Predicate<? super T> predicate) Map<Boolean, List> 根據 true 或 false 進行分割槽 list.stream().collect(Collectors.partitioningBy(Employee::getStatus));

示例:

public class StreamAPITest {
    // 3 - 收集
    @Test
    public void test4() {
        // collect(Collector c) --- 將流轉換為其他形式,接收一個 Collector 介面的實現,用於給 Stream 中元素做彙總的方法
        // 練習:查詢工資大於 6000 的員工,結果返回為一個 List 或 Set

        List<Employee> employees = EmployeeData.getEmployees();

        // 返回 List
        List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
        employeeList.forEach(System.out::println);

        System.out.println("************************");

        // 返回 Set
        Set<Employee> employeeSet = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
        employeeSet.forEach(System.out::println);
    }

    // 4 - 將 List 轉變為逗號分隔的字串
    @Test
    public void test5() {
        List<String> list = Arrays.asList("A", "B", "C", "D", "E");

        // String 類的方法,推薦
        String join = String.join(",", list);
        System.out.println(join);

        // Java 8 流式寫法
        String collect = list.stream().collect(Collectors.joining(","));
        System.out.println(collect);

        // 常規寫法,使用迴圈遍歷新增,此處不表
    }
}

原文連結

https://github.com/ACatSmiling/zero-to-zero/blob/main/JavaLanguage/java.md

相關文章