java8中的方法引用(Method Reference)

本文旨在用通俗易懂的語言講解Java8中引入的一個語法糖--方法引用(method reference)

什么是方法引用?

對一個類某個方法進行引用。形式大致為:

  • 類型::方法名(構造方法為類型::new)
  • 對象::方法名

例子:

  • String的靜態方法valueOf對應的方法引用為String::valueOf
  • Object的構造方法對應的方法引用為Object::new
  • 調用對象o的實例方法hashCode對應的方法引用為o::hashCode

方法引用有什么用?

在講方法引用有什么用之前,先介紹一下lambda表達式

Lambda表達式

在Java8出現之前,當我們需要創建某個接口的一個實例時,通常的做法是:

  • 創建接口的一個實現類,然后通過該類來創建實例

    • interface A {
          void say(String s);
      }
      
      class AImpl implemnets A {
          @Override
          public void say(String s) {
              System.out.println(s);
          }
      }
      A a = new AImpl();
      
  • 通過匿名內部類的形式

    • interface A {
          void say(String s);
      }
      A a = new A() {
          @Override
          public void say(String s) {
             System.out.println(s);
          }
      }
      

如果實現類只會被使用一次,通過匿名內部類的方式更簡潔。

有相當一部分接口實際上只有一個抽象方法(default方法不是抽像方法),對于這樣的接口,我們稱之為函數式接口。比如ComparatorRunnable等接口。

image

jdk中的函數式接口的聲明處一般都有@FunctionalInterface注解,加上這個注解的接口,如果不滿足函數式接口的規范(只有一個抽象方法),編譯器就會報錯。

image
image

對于函數式接口,Java8引入lambda表達式來進一步簡化匿名內部類的寫法,因此非函數式接口是不能用lambda表達式的形式來創建接口的實例。

lambda表達式在許多語言中都有,比如在JavaScript中是=>表示的函數寫法,在Java中則是->

Lambda表達式的形式:

(參數1,參數2,...) -> {
    // 抽象方法實現的代碼塊
    ...
}
  1. 如果參數只有1個,則可以省略掉括號
  2. 如果代碼塊中只有一行代碼,則可以省略掉花括號和代碼塊結尾的分號
  3. 如果代碼塊中只有一條語句,且該語句為return語句,則可以將return省略

一個比較簡短的lambda表達式長這樣:

a -> a+1
() -> System.out.println("hello world!")

進一步簡化lambda表達式

方法引用的引出是為了簡化代碼,簡化什么代碼呢?答案就是簡化lambda表達式,而且是對于只有一行代碼的lambda表達式。下面來看幾個案例:

  1. 將一個整型數字轉換成對應的字符串
// 接口
interface A {
   String m(Integer i);
}

// 創建A的一個實例,lambda表達式寫法
A a = i -> String.valueOf(i);
a.m(1); // 輸出 "1"
  1. 將一個整型字符串轉換成整型數字
// 接口
interface A {
   Integer m(String s);
}

// 創建A的一個實例,lambda表達式寫法
A a = s -> Integer.valueOf(s);
a.m("1"); // 輸出 1

對于上面的兩個lambda表達式,都是這樣的一種情況:lambda的形參作為某個靜態方法的實參傳入,在實際編程中有太多類似的這樣的情況,因此對于這種代碼,引入了方法引用進行簡化,以上兩個lambda表達式用方法引用的寫法如下:

A a = String::valueOf
A a = Integer::valueOf

方法引用的幾種形式

下面介紹我總結的幾種方法引用的轉換形式:

  • 類名::靜態方法名
  • 對象::實例方法名
  • 類名::實例方法名
  • 類型::new(構造方法的引用)

類名::靜態方法名

lambda表達式中調用某個類的靜態方法,且lambda的形參作為靜態方法的參數傳入,并且lambda的方法返回類型要和靜態方法的返回類型對應上。如:

str -> Integer.parseInt(str) 
對應的方法引用:
Integer::parseInt
image

采用擦除法,去掉左右兩邊一致的參數表

對象::實例方法名

lambda表達式中調用某個對象的某個方法,并且lambda的形參作為該方法的實參,并且lambda的方法返回類型要和實例方法的返回類型對應。如:

class A {
  void a(String s) {
    System.out.println(s);
  }
}

interface B {
  void b(String s);
}

A a = new A();
B b = s -> a.a(s); // 等價于 B b = a::a;
image

同樣是擦除法記憶,去掉左右兩邊一致的參數表

類名::實例方法名

lambda表達式中調用lambda形參中第一個參數的某個實例方法,并且lambda形參剩余的n-1個參數作為這個實例方法的實參,并且lambda的方法返回類型要和實例方法的返回類型對應。

str -> str.toLowerCase(); // 對應方法引用寫法:String::toLowerCase
image

類型::new

類型我們分為基本數據類型和引用類型.

  1. 基本數據類型

對于普通基本數據類型沒有new的操作,但是創建對應的數組則是通過關鍵字new來完成,lambda的形參作為數組的長度傳入,比如:

image
  1. 引用類型

lambda的形參作為某個類的構造方法的實參,如:

image

總結

方法引用可使得Java代碼編寫起來更加簡短,所有方法引用的寫法都需要滿足lambda方法的返回值類型與方法引用的返回值類型一致,即:

  • lambda 方法返回值類型為void ,則方法引用的返回值可以是void或者非void

  • lambda方法返回值非void,則方法引用的返回值類型要保持相同或者符合里氏置換原則(LSP)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。