Java 之 方法引用


方法引用

一、冗余的Lambda場景

  來看一個簡單的函數式接口以應用Lambda表達式:

1 @FunctionalInterface 2 public interface Printable { 3     void print(String str); 4 }

 

  Printable 接口當中唯一的抽象方法 print 接收一個字符串參數,目的就是為了打印顯示它。那么通過Lambda來使用它的代碼很簡單:

1 public class Demo01PrintSimple { 2   private static void printString(Printable data) { 3     data.print("Hello, World!"); 4   } 5   public static void main(String[] args) { 6     printString(s ‐> System.out.println(s)); 7   } 8 }

   其中 printString 方法只管調用 Printable 接口的 print 方法,而並不管 print 方法的具體實現邏輯會將字符串打印到什么地方去。

   而 main 方法通過Lambda表達式指定了函數式接口 Printable 的具體操作方案為:拿到String(類型可推導,所以可省略)數據后,在控制台中輸出它

二、問題分析

  這段代碼的問題在於,對字符串進行控制台打印輸出的操作方案,明明已經有了現成的實現,那就是 System.out對象中的 println(String) 方法。

   既然Lambda希望做的事情就是調用 println(String) 方法,那何必自己手動調用呢?

三、用方法引用改進代碼

  能否省去Lambda的語法格式(盡管它已經相當簡潔)呢?只要引用過去就好了: 

1  public class DemoPrintRef { 2         private static void printString(Printable data) { 3             data.print("Hello, World!"); 4  } 5         public static void main(String[] args) { 6  printString(System.out::println); 7  } 8     }

  請注意其中的雙冒號 :: 寫法,這被稱為方法引用,而雙冒號是一種新的語法。

四、方法引用符

  雙冒號 :: 為引用運算符,而它所在的表達式被稱為方法引用。如果Lambda要表達的函數方案已經存在於某個方法的實現中,那么則可以通過雙冒號來引用該方法作為Lambda的替代者。

  1、語義分析

      例如上例中, System.out 對象中有一個重載的 println(String) 方法恰好就是我們所需要的。那么對於printString 方法的函數式接口參數,對比下面兩種寫法,完全等效:

      •   Lambda表達式寫法: s -> System.out.println(s);
      •   方法引用寫法: System.out::println

       第一種語義是指:拿到參數之后經Lambda之手,繼而傳遞給 System.out.println 方法去處理。

      第二種等效寫法的語義是指:直接讓 System.out 中的 println 方法來取代Lambda。兩種寫法的執行效果完全樣,而第二種方法引用的寫法復用了已有方案,更加簡潔

      注意:Lambda 中 傳遞的參數 一定是方法引用中 的那個方法可以接收的類型,否則會拋出異常

  2、推導與省略

      如果使用Lambda,那么根據可推導就是可省略的原則,無需指定參數類型,也無需指定的重載形式——它們都將被自動推導。而如果使用方法引用,也是同樣可以根據上下文進行推導。

      函數式接口是Lambda的基礎,而方法引用是Lambda的孿生兄弟。

     下面這段代碼將會調用 println 方法的不同重載形式,將函數式接口改為int類型的參數:

1 @FunctionalInterface 2 public interface PrintableInteger { 3     void print(int str); 4 }

      由於上下文變了之后可以自動推導出唯一對應的匹配重載,所以方法引用沒有任何變化:

1  public class DemoPrintOverload { 2         private static void printInteger(PrintableInteger data) { 3             data.print(1024); 4  } 5         public static void main(String[] args) { 6  printInteger(System.out::println); 7  } 8     }

     這次方法引用將會自動匹配到 println(int) 的重載形式 

五、通過對象名引用成員方法

  這是最常見的一種用法,與上例相同。如果一個類中已經存在了一個成員方法:

1 public class MethodRefObject { 2   public void printUpperCase(String str) { 3     System.out.println(str.toUpperCase()); 4   } 5 }

  函數式接口仍然定義為:

1 @FunctionalInterface 2 public interface Printable { 3   void print(String str); 4 }

 

  那么當需要使用這個 printUpperCase 成員方法來替代 Printable 接口的Lambda的時候,已經具有了MethodRefObject 類的對象實例,則可以通過對象名引用成員方法,代碼為:

1 public class DemoMethodRef { 2   private static void printString(Printable lambda) { 3     lambda.print("Hello"); 4   } 5   public static void main(String[] args) { 6     MethodRefObject obj = new MethodRefObject(); 7     printString(obj::printUpperCase); 8   } 9 }

 

六、通過類名稱引用靜態方法

  由於在 java.lang.Math 類中已經存在了靜態方法 abs ,所以當我們需要通過Lambda來調用該方法時,有兩種寫法。首先是函數式接口:

1 @FunctionalInterface 2 public interface Calcable { 3   int calc(int num); 4 }

  第一種寫法是使用Lambda表達式:

1 public class DemoLambda { 2   private static void method(int num, Calcable lambda) { 3     System.out.println(lambda.calc(num)); 4   } 5   public static void main(String[] args) { 6     method(‐10, n ‐> Math.abs(n)); 7   } 8 }

  但是使用方法引用的更好寫法是:

1 public class Demo06MethodRef { 2   private static void method(int num, Calcable lambda) { 3     System.out.println(lambda.calc(num)); 4   } 5   public static void main(String[] args) { 6     method(‐10, Math::abs); 7   } 8 }

  在這個例子中,下面兩種寫法是等效的:

    •  Lambda表達式: n -> Math.abs(n)
    •     方法引用: Math::abs 

七、通過super引用成員方法

  如果存在繼承關系,當Lambda中需要出現super調用時,也可以使用方法引用進行替代。首先是函數式接口:

1 @FunctionalInterface 2 public interface Greetable { 3   void greet(); 4 }

  然后是父類 Human 的內容:

1 public class Human { 2   public void sayHello() { 3     System.out.println("Hello!"); 4   } 5 }

   最后是子類 Man 的內容,其中使用了Lambda的寫法:

 1 public class Man extends Human {  2  @Override  3         public void sayHello() {  4             System.out.println("大家好,我是Man!");  5  }  6         //定義方法method,參數傳遞Greetable接口
 7         public void method(Greetable g){  8  g.greet();  9  } 10         public void show(){ 11             //調用method方法,使用Lambda表達式
12             method(()‐>{ 13                     //創建Human對象,調用sayHello方法
14                     new Human().sayHello(); 15  }); 16             //簡化Lambda
17             method(()‐>new Human().sayHello()); 18             //使用super關鍵字代替父類對象
19             method(()‐>super.sayHello()); 20  } 21     }

  

  但是如果使用方法引用來調用父類中的 sayHello 方法會更好,例如另一個子類 Woman

 1 public class Man extends Human {  2  @Override  3         public void sayHello() {  4             System.out.println("大家好,我是Man!");  5  }  6         //定義方法method,參數傳遞Greetable接口
 7         public void method(Greetable g){  8  g.greet();  9  } 10         public void show(){ 11             method(super::sayHello); 12  } 13     }

  在這個例子中,下面兩種寫法是等效的:

    •  Lambda表達式: () -> super.sayHello()
    •     方法引用: super::sayHello 

八、通過this引用成員方法

  this代表當前對象,如果需要引用的方法就是當前類中的成員方法,那么可以使用this::成員方法的格式來使用方法引用。首先是簡單的函數式接口:

1 @FunctionalInterface 2 public interface Richable { 3   void buy(); 4 }

    下面是一個丈夫 Husband 類:

1 public class Husband { 2   private void marry(Richable lambda) { 3     lambda.buy(); 4   } 5   public void beHappy() { 6     marry(() ‐> System.out.println("買套房子")); 7   } 8 }

   開心方法 beHappy 調用了結婚方法 marry ,后者的參數為函數式接口 Richable ,所以需要一個Lambda表達式。
  但是如果這個Lambda表達式的內容已經在本類當中存在了,則可以對 Husband 丈夫類進行修改:

 1 public class Husband {  2   private void buyHouse() {  3     System.out.println("買套房子");  4   }  5   private void marry(Richable lambda) {  6     lambda.buy();  7   }  8   public void beHappy() {  9     marry(() ‐> this.buyHouse()); 10   } 11 }

 

  如果希望取消掉Lambda表達式,用方法引用進行替換,則更好的寫法為:

 1 public class Husband {  2   private void buyHouse() {  3     System.out.println("買套房子");  4   }  5   private void marry(Richable lambda) {  6     lambda.buy();  7   }  8   public void beHappy() {  9     marry(this::buyHouse); 10   } 11 }

 

  在這個例子中,下面兩種寫法是等效的:

    •    Lambda表達式: () -> this.buyHouse()
    •    方法引用: this::buyHouse

九、類的構造器引用

  由於構造器的名稱與類名完全一樣,並不固定。所以構造器引用使用 類名稱::new 的格式表示。首先是一個簡單的 Person 類:

 1 public class Person {  2   private String name;  3     public Person(String name) {  4     this.name = name;  5 }  6   public String getName() {  7     return name;  8 }  9   public void setName(String name) { 10     this.name = name; 11 } 12 }

 

  然后是用來創建 Person 對象的函數式接口:

1 public interface PersonBuilder { 2   Person buildPerson(String name); 3 }

 

  要使用這個函數式接口,可以通過Lambda表達式:

1 public class DemoLambda { 2   public static void printName(String name, PersonBuilder builder) { 3     System.out.println(builder.buildPerson(name).getName()); 4   } 5   public static void main(String[] args) { 6     printName("趙麗穎", name ‐> new Person(name)); 7   } 8 }

 

  但是通過構造器引用,有更好的寫法:

1 public class Demo10ConstructorRef { 2   public static void printName(String name, PersonBuilder builder) { 3     System.out.println(builder.buildPerson(name).getName()); 4   } 5   public static void main(String[] args) { 6     printName("趙麗穎", Person::new); 7   } 8 }

  在這個例子中,下面兩種寫法是等效的:

    •  Lambda表達式: name -> new Person(name)
    •    方法引用: Person::new

十、數組的構造器引用

  數組也是 Object 的子類對象,所以同樣具有構造器,只是語法稍有不同。如果對應到Lambda的使用場景中時,需要一個函數式接口:

1 @FunctionalInterface 2 public interface ArrayBuilder { 3   int[] buildArray(int length); 4 }

  在應用該接口的時候,可以通過Lambda表達式:

1 public class DemoArrayInitRef { 2   private static int[] initArray(int length, ArrayBuilder builder) { 3     return builder.buildArray(length); 4   } 5   public static void main(String[] args) { 6     int[] array = initArray(10, length ‐> new int[length]); 7   } 8 }

 

  但是更好的寫法是使用數組的構造器引用 :

1 public class DemoArrayInitRef { 2   private static int[] initArray(int length, ArrayBuilder builder) { 3     return builder.buildArray(length); 4   } 5   public static void main(String[] args) { 6   int[] array = initArray(10, int[]::new); 7   } 8 }

   在這個例子中,下面兩種寫法是等效的:

    •   Lambda表達式: length -> new int[length]
    •     方法引用: int[]::new

       

 


免責聲明!

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



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