1. 程式人生 > 其它 >Java中的函數語言程式設計(四)方法引用method reference

Java中的函數語言程式設計(四)方法引用method reference

寫在前面

我們已經知道,lambda表示式是一個匿名函式,可以用lambda表示式來實現一個函式式介面。 很自然的,我們會想到類的方法也是函式,本質上和lambda表示式是一樣的,那是否也可以用類的方法來實現一個函式式介面呢?答案是可以的。我們稱之為方法引用(methodreference)。 本文的示例程式碼可從gitee上獲取:https://gitee.com/cnmemset/javafp

方法引用

一個典型例子,向一個Map中寫入單詞以及它的長度:
public static void simpleMethodReference() {
    Map<String, Integer> wordMap = new
HashMap<>(); // 等同於 wordMap.computeIfAbsent("hello", s -> s.length()); wordMap.computeIfAbsent("hello", String::length); // 輸出為 {hello=5} System.out.println(wordMap); }
上述程式碼中,String::length就是方法引用,它用:: 來分割類名或物件與方法名,:: 左側是類名或物件,:: 右側是方法名。 一般來說,方法引用有4種情況: 1.object::instanceMethod ——物件+ 例項方法 2.Class::staticMethod ——類名+ 靜態方法 3.Class::instanceMethod——類名+ 例項方法 4.Class::new ——類名+ 關鍵字new,這種情況又稱為構造器引用(constructor reference)

1. object::instanceMethod

object::instanceMethod,:: 左側是一個物件,:: 右側是例項方法名。 它等價於提供了 instanceMethod 方法的引數列表的 lambda表示式。 形象來說,假設方法 instanceMethod 的引數列表為 (x, y),那麼 object::instanceMethod 等價於 (x, y) -> object.instanceMethod(x, y) 。 例如對於字串 str (String str = ""): str::compareTo等價於s -> str.compareTo(s) 示例程式碼如下:
public static
void objectInstanceMethodReference() { String me = "me"; // wordMap 的 key 是給定的單詞,value是不區分大小寫,與單詞 "me" 比較後得出的值 Map<String, Integer> wordMap = new HashMap<>(); // me::compareToIgnoreCase 等價於 s -> me.compareToIgnoreCase(s) wordMap.computeIfAbsent("him", me::compareToIgnoreCase); wordMap.computeIfAbsent("you", s -> me.compareToIgnoreCase(s)); System.out.println(wordMap); }
上述程式碼的輸出是: {him=5, you=-12}

2. Class::staticMethod

Class::staticMethod,:: 左側是一個類,:: 右側是靜態方法名。 它等價於提供了staticMethod方法的引數列表的lambda表示式。 形象來說,假設靜態方法 staticMethod 的引數列表為 (x, y),那麼 Class::staticMethod 等價於 (x, y, z) -> Class.staticMethod(x, y) 。 例如: System.out::println 等價於x -> System.out.print(x) 示例程式碼:
public static void classStaticMethodReference() {
    List<String> list = Arrays.asList("Guangdong", "Zhejiang", "Jiangsu");

    // System.out::println 等價於 s -> System.out.println(s)
    list.forEach(System.out::println);
}
上述程式碼輸出為: Guangdong Zhejiang Jiangsu

3. Class::instanceMethod

對於Class::instanceMethod,:: 左側是一個類,:: 右側是例項方法名。 假設instanceMethod的引數列表是(x, y),那麼Class::instanceMethod等價於lambda表示式(obj, x, y) -> obj.instanceMethod(x, y),其中obj是Class的物件例項。 例如: String::length 等價於s-> s.length() String::compareToIgnoreCase等價於(s1, s2) -> s1.compareToIgnoreCase(s2) 示例程式碼:
public static void classInstanceMethodReference() {
    Map<String, Integer> wordMap = new HashMap<>();
    Integer wordLen = wordMap.computeIfAbsent("hello", String::length);
    System.out.println(wordMap);
}
上述程式碼輸出為: {hello=5}

4. Class::new

對於Class::new,new的含義是指Class的建構函式,所以又稱為構造器引用(constructor reference)。 假設Class的建構函式有兩個,它們的引數列表分別是(x)和(x, y),那麼Class::new可能等價於x-> new Class(x),也有可能等價於(x, y) -> new Class(x, y),具體是哪個,編譯器會在編譯階段通過上下文推斷出來。 例如: BigDecimal::new ,根據上下文,可能等價於(String s) -> new BigDecimal(s) 特別的,陣列型別也可以使用構造器引用。陣列型別只有一個構造引數,表示陣列的長度: String[]::new 等價於x -> new String[x] 示例程式碼:
public static void ctorMethodReference() {
    List<String> list = Arrays.asList("1.1", "2.2", "3.3");

    // BigDecimal::new 根據上下文推斷,等價於 s -> new BigDecimal(s)
    Stream<BigDecimal> stream = list.stream().map(BigDecimal::new);
    List<BigDecimal> decimalList = stream.collect(Collectors.toList());
    System.out.println(decimalList);

    // 構建一個新的 Stream ,之前的 Stream 已經被關閉了
    Stream<BigDecimal> stream1 = list.stream().map(BigDecimal::new);

    // BigDecimal[]::new ,陣列的構造器引用,等價於 x -> new BigDecimal[x]
    BigDecimal[] decimalArray = stream1.toArray(BigDecimal[]::new);
    for (BigDecimal d : decimalArray) {
        System.out.println(d);
    }
}
上述程式碼的輸出為: [1.1, 2.2, 3.3] 1.1 2.2 3.3

5. this::instanceMethod和super::instanceMethod

對於this::instanceMethod,很容易理解,相當於把this關鍵字看做是當前類的例項物件即可。 例如: this::equals 等價於x -> this.equals(x) 對於super::instanceMethod,會相對複雜一些,相當於在this物件上,呼叫的指定方法父類版本。 示例程式碼:
public class SuperMethodReferenceExample {
    public static void main(String[] args) {
        ThreadWaiter waiter = new ThreadWaiter();
        waiter.run();
    }
 
    public static class Waiter {
        public void sayHi() {
            System.out.println("Hello, man!");
        }
    }
 
    public static class ThreadWaiter extends Waiter {
        @Override
        public void sayHi() {
            System.out.println("Hello, thread!");
        }
 
        public void run() {
            // 指定呼叫父類 Waiter 的 sayHi 方法
            Thread t = new Thread(super::sayHi);
            t.start();
        }
    }
}

上述程式碼的輸出為:

Hello, man!

結語

方法引用可以視為lambda表示式的一個語法糖。