Java中的函數式編程(四)方法引用method reference


寫在前面

我們已經知道,lambda表達式是一個匿名函數,可以用lambda表達式來實現一個函數式接口。
 
很自然的,我們會想到類的方法也是函數,本質上和lambda表達式是一樣的,那是否也可以用類的方法來實現一個函數式接口呢?答案是可以的。我們稱之為方法引用(method reference)。
 
本文的示例代碼可從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) -> 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表達式的一個語法糖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM