CodeGym /課程 /JAVA 25 SELF /Method References (::):方法參照

Method References (::):方法參照

JAVA 25 SELF
等級 21 , 課堂 1
開放

1. 引言

Method reference(中文可譯作「方法參照」)是一種 Java 的特殊語法,允許將既有的方法(或建構函式)作為函式式介面的實作來傳遞。只要簽章一致,就能在需要 lambda 表達式的地方「傳遞」方法。

語法:

Klass::metod
obekt::metod
Klass::new

如果說 lambda 是「隨寫即用」的小函式,那麼 method reference 就是「直接傳遞既有的方法」。這就像與其重抄食譜,不如給對方一個食譜頁面的連結。

直觀範例

與其這樣:

list.forEach(s -> System.out.println(s));

可以這樣:

list.forEach(System.out::println);

看起來很精簡,對吧?

2. method reference 的種類

方法參照有四種主要形式。下面這些就足夠了。

參照靜態方法

語法: Klass::staticheskiyMetod

範例:

Function<Integer, String> intToString = String::valueOf;
System.out.println(intToString.apply(123)); // "123"

使用 lambda 等價寫法:

Function<Integer, String> intToString = i -> String.valueOf(i);

參照物件的非靜態方法

語法: obekt::metod

範例:

PrintStream printer = System.out;
Consumer<String> consumer = printer::println;
consumer.accept("你好,世界!");

等價於 lambda:

Consumer<String> consumer = s -> printer.println(s);

參照類別的非靜態方法

語法: Klass::metod

此處函式式介面的第一個參數會成為呼叫該方法的物件。

範例:

Function<String, Integer> stringLength = String::length;
System.out.println(stringLength.apply("Java")); // 4

此處的 String::length 會被轉換成函式: (String s) -> s.length()

參照建構函式

語法: Klass::new

範例:

Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();

等價於 lambda:

Supplier<ArrayList<String>> listSupplier = () -> new ArrayList<>();

3. 何時使用 method reference?

當 lambda 只是呼叫既有方法、沒有額外邏輯時,使用 method reference 能讓程式碼更短、更易讀。

範例:排序清單

與其這樣:

List<String> names = Arrays.asList("伊萬", "彼得", "安娜");
names.sort((a, b) -> a.compareToIgnoreCase(b));

可以這樣:

names.sort(String::compareToIgnoreCase);

範例:處理集合

與其:

list.forEach(s -> System.out.println(s));

可以更精簡:

list.forEach(System.out::println);

範例:元素轉換

與其:

List<String> numbers = Arrays.asList("1", "2", "3");
List<Integer> ints = numbers.stream()
    .map(s -> Integer.parseInt(s))
    .collect(Collectors.toList());

可以改為:

List<Integer> ints = numbers.stream()
    .map(Integer::parseInt)
    .collect(Collectors.toList());

關於 Stream API 與工具類別 Collectors 的更多內容,你會在第 30 級學到 :P

4. method reference 與 lambda 表達式的比較

等價性

method reference 與 lambda 表達式通常可以互換。當簽章一致時,兩者都能實作函式式介面。

範例:

Consumer<String> c1 = s -> System.out.println(s);
Consumer<String> c2 = System.out::println;

什麼時候更適合用 method reference?

  • 當 lambda 只是呼叫既有方法,且沒有額外邏輯。
  • 為了提高可讀性,尤其在很長的呼叫鏈中。
  • 想要明確表達「這裡只是呼叫方法」。

什麼時候不適合用 method reference?

  • 需要額外邏輯(驗證、條件、錯誤處理)時。
  • 在呼叫方法前需要先轉換參數時。

範例:

list.forEach(s -> {
    if (s != null) System.out.println(s);
});
// 這裡不適合用 method reference,只能用 lambda。

5. 實作練習:把 lambda 重寫為 method reference

範例 1:輸出動物名稱

List<String> animals = Arrays.asList("貓", "狗", "鸚鵡");
animals.forEach(animal -> System.out.println(animal));

重寫為:

animals.forEach(System.out::println);

範例 2:把字串轉成數字

List<String> numbers = Arrays.asList("10", "20", "30");
List<Integer> ints = numbers.stream()
    .map(s -> Integer.parseInt(s))
    .collect(Collectors.toList());

重寫為:

List<Integer> ints = numbers.stream()
    .map(Integer::parseInt)
    .collect(Collectors.toList());

範例 3:依名稱排序物件

List<Animal> animalList = ...;
animalList.sort((a, b) -> a.getName().compareTo(b.getName()));

重寫為:

animalList.sort(Comparator.comparing(Animal::getName));

這裡的 Animal::getName 是對類別非靜態方法的參照。

範例 4:透過建構函式建立物件

Supplier<Dog> dogFactory = () -> new Dog();
Dog dog = dogFactory.get();

重寫為:

Supplier<Dog> dogFactory = Dog::new;
Dog dog = dogFactory.get();

6. 簽章匹配如何運作

只有當方法的簽章與函式式介面的抽象方法一致時,才能使用 method reference。

@FunctionalInterface
interface IntToString {
    String convert(int value);
}

public class Demo {
    public static String intToHex(int value) {
        return Integer.toHexString(value);
    }

    public static void main(String[] args) {
        IntToString converter = Demo::intToHex;
        System.out.println(converter.convert(255)); // ff
    }
}

此處 Demo::intToHex 適用,因為它接受 int 並回傳 String

7. 方法參照與帶參數的建構函式

即使建構函式帶有參數,只要簽章一致,仍然可以使用 method reference。

@FunctionalInterface
interface AnimalFactory {
    Animal create(String name);
}

class Animal {
    private String name;
    public Animal(String name) { this.name = name; }
    public String getName() { return name; }
}

AnimalFactory factory = Animal::new;
Animal cat = factory.create("Barsik");
System.out.println(cat.getName()); // Barsik

8. 使用 method reference 的常見錯誤

錯誤 1:簽章不匹配。
如果方法的簽章與介面的抽象方法不一致,編譯器會報錯。比如介面期望兩個參數,但參照到的卻是只有一個參數的方法。

錯誤 2:嘗試在沒有物件的情況下使用類別的非靜態方法參照。
當使用 Klass::metod 這種形式時,介面的第一個參數會變成呼叫該方法的物件。如果把參數的數量或順序搞錯,就會匹配失敗。

錯誤 3:在需要額外邏輯的地方使用 method reference。
如果需要條件、記錄(logging)或例外處理,請使用 lambda,而不是方法參照。

錯誤 4:參照到多載的方法。
當類別中存在多個同名方法時,編譯器可能無法判斷該選哪一個。有時需要明確標註函式式介面的型別來幫助推斷。

錯誤 5:在沒有物件的情境下使用非靜態方法參照。
例如,String::toUpperCasemap 中能正確運作,因為第一個參數就是 String 物件本身。但若在需要靜態方法的情境中使用它,就會導致錯誤。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION