JDK8

Java8之Consumer、Supplier、Predicate和Function攻略

參考資料:(https://kknews.cc/zh-tw/code/jl4ba6q.html)

這幾個接口都在 java.util.function 包下的,分別是Consumer(消費型)、supplier(供給型)、predicate(謂詞型)、function(功能性),相信有了後面的解釋,你應該非常清楚這個接口的功能了。

Consumer

接收參數,並"使用該參數"的一種型別

/**
 * consumer接口測試
 */
@Test
public void test_Consumer() {
    //① 使用consumer接口實現方法
    Consumer<String> consumer = new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    };

    Stream<String> stream = Stream.of("aaa", "bbb", "ddd", "ccc", "fff");
    stream.forEach(consumer);

    //② 使用lambda表達式,forEach方法需要的就是一個Consumer接口
    System.out.println("********************");
    stream = Stream.of("aaa", "bbb", "ddd", "ccc", "fff");
    Consumer<String> consumer1 = (s) -> System.out.println(s);
    //lambda表達式返回的就是一個Consumer接口
    stream.forEach(consumer1);
    // 更直接的方式
    // stream.forEach((s) -> System.out.println(s));
    System.out.println("********************");

    //③ 使用方法引用,方法引用也是一個consumer
    stream = Stream.of("aaa", "bbb", "ddd", "ccc", "fff");
    Consumer consumer2 = System.out::println;
    stream.forEach(consumer);
    //更直接的方式
    // stream.forEach(System.out::println);
}

Supplier

"提供某種參數"的型別、容器

範例一

/**
 * Supplier接口測試,supplier相當一個容器或者變量,可以存儲值
 */
@Test
public void test_Supplier() {
    //① 使用Supplier接口實現方法,只有一個get方法,無參數,返回一個值
    Supplier<Integer> supplier = new Supplier<Integer>() {
        @Override
        public Integer get() {
            //返回一個隨機值
            return new Random().nextInt();
        }
    };

    System.out.println(supplier.get());
    System.out.println("********************");
    //② 使用lambda表達式,
    supplier = () -> new Random().nextInt();
    System.out.println(supplier.get());
    System.out.println("********************");
    //③ 使用方法引用
    Supplier<Double> supplier2 = Math::random;
    System.out.println(supplier2.get());
}

範例二(提供給Optional)

/**
 * Supplier接口測試2,使用需要Supplier的接口方法
 */
@Test
public void test_Supplier2() {
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
    //返回一個optional對象
    Optional<Integer> first = stream.filter(i -> i > 4).findFirst();
    //optional對象有需要Supplier接口的方法
    //orElse,如果first中存在數,就返回這個數,如果不存在,就放回傳入的數
    System.out.println(first.orElse(1));
    System.out.println(first.orElse(7));
    System.out.println("********************");
    Supplier<Integer> supplier = new Supplier<Integer>() {
        @Override
        public Integer get() {
            //返回一個隨機值
            return new Random().nextInt();
        }
    };
    //orElseGet,如果first中存在數,就返回這個數,如果不存在,就返回supplier返回的值
    System.out.println(first.orElseGet(supplier));
}

Predicate

判斷,提供一種"條件判斷"的一種方法

範例一

/**
 * Predicate謂詞測試,謂詞其實就是一個判斷的作用類似bool的作用
 */
@Test
public void test_Predicate() {
    //① 使用Predicate接口實現方法,只有一個test方法,傳入一個參數,返回一個bool值
    Predicate<Integer> predicate = new Predicate<Integer>() {
        @Override
        public boolean test(Integer integer) {
            if (integer > 5) {
                return true;
            }
            return false;
        }
    };

    System.out.println(predicate.test(6));
    System.out.println("********************");

    //② 使用lambda表達式
    predicate = (t) -> t > 5;
    System.out.println(predicate.test(1));
    System.out.println("********************");
}

範例二

/**
 * Predicate謂詞測試,Predicate作為接口使用
 */
@Test
public void test_Predicate2() {
    //① 將Predicate作為filter接口,Predicate起到一個判斷的作用
    Predicate<Integer> predicate = new Predicate<Integer>() {
        @Override
        public boolean test(Integer integer) {
            if (integer > 5) {
                return true;
            }
            return false;
        }
    };

    Stream<Integer> stream = Stream.of(1, 23, 3, 4, 5, 56, 6, 6);
    List<Integer> list = stream.filter(predicate).collect(Collectors.toList());
    list.forEach(System.out::println);
    System.out.println("********************");

    // lambda 寫法
    Predicate<Integer> predicate2 = integer -> {
        return integer > 5;
    };
    Stream<Integer> stream2 = Stream.of(1, 23, 3, 4, 5, 56, 6, 6);
    List<Integer> list2 = stream2.filter(predicate2).collect(Collectors.toList());
    list2.forEach(System.out::println);
}

Function

功能型接口,將參數進行轉換的一種方法

範例一

/**
 * Function測試,function的作用是轉換,將一個值轉為另外一個值
 */
@Test
public void test_Function() {
    //① 使用map方法,泛型的第一個參數是轉換前的類型,第二個是轉化後的類型
    Function<String, Integer> function = new Function<String, Integer>() {
        @Override
        public Integer apply(String s) {
            //獲取每個字符串的長度,並且返回
            return s.length();
        }
    };

    Stream<String> stream = Stream.of("aaa", "bbbbb", "ccccccv");
    Stream<Integer> stream1 = stream.map(function);
    stream1.forEach(System.out::println);
    System.out.println("********************");

    // lambda
    Function<String, Integer> function2 = s -> {
        //獲取每個字符串的長度,並且返回
        return s.length();
    };

    Stream<String> stream2 = Stream.of("aaa", "bbbbb", "ccccccv");
    Stream<Integer> stream3 = stream2.map(function2);
    stream3.forEach(System.out::println);
    System.out.println("********************");
}

額外參考資料:(https://matthung0807.blogspot.com/2018/09/java-8-javautilfunction-functional.html)

額外參考資料:(https://openhome.cc/Gossip/Java/ConsumerFunctionPredicateSupplier.html)

使用 Optional 取代 null

參考資料:(https://openhome.cc/Gossip/Java/Optional.html)

過去null太過被濫用,因為 null 的模糊不清、模稜兩可,導致 Java 工程師總是要跟NullPointerException奮戰。

null的最根本問題在於語意含糊不清,雖然就字面來說,null可以是「不存在」、「沒有」、「無」或「空」的概念,因此在應用時,總是令人感到模稜兩可,也就讓開發者有了各自解釋的空間,當開發者想到「嘿!這邊可以沒有東西…」就直接放個null,或者是想到「嗯!沒什麼東西可以傳回…」,就不假思索地傳回個null,然後使用者就總是忘了檢查null,引發各種可能的錯誤。

原本會回傳null的寫法

static String getNickName(String name) {
    Map<String, String> nickNames = new HashMap<>(); // 假裝的鍵值資料庫
    nickNames.put("Justin", "caterpillar");
    nickNames.put("Monica", "momor");
    nickNames.put("Irene", "hamimi");
    return nickNames.get(name); // 鍵不存在時會傳回null
}

在上面的程式中,如果呼叫getNickName()時忘了檢查null,那麼就會直接顯示null,在這個簡單的例子中並不會怎樣,只是顯示結果令人困惑罷了,但如果後續的執行流程牽涉到至關重要的結果,程式快樂地繼續執行下去,錯誤可能到最後才會呈現發生。

改寫成回傳Optiona的寫法

static Optional<String> getNickName(String name) {
    Map<String, String> nickNames = new HashMap<>();
    nickNames.put("Justin", "caterpillar");
    nickNames.put("Monica", "momor");
    nickNames.put("Irene", "hamimi");
    String nickName = nickNames.get(name);
    return nickName == null ? Optional.empty() : Optional.of(nickName);
}

因為呼叫getNickName()時傳回的是Optional型態的實例,語義上表示它包含也可能不包括值,客戶端就要意識必須進行檢查,如果不檢查就直接呼叫Optionalget()方法:

String nickName = getNickName("Duke").get();
System.out.println(nickName);

Optional沒有包含值的情況下,就會直接拋出java.util.NoSuchElementException,這實現了速錯(Fail fast)的概念,這讓開發者可以立即發現錯誤,並瞭解到必須使用程式碼作些檢查。

可能的方式之一像是:

Optional<String> nickOptional = getNickName("Duke");
String nickName = "Openhome Reader";
if(nickOptional.isPresent()) {
    nickName = nickOptional.get();
}
System.out.println(nickName);

//或是寫成這樣
Optional<String> nickOptional = getNickName("Duke");
System.out.println(nickOptional.orElse("Openhome Reader"));

Last updated