本文旨在用通俗易懂的語言講解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方法不是抽像方法),對于這樣的接口,我們稱之為函數式接口。比如Comparator
、Runnable
等接口。
jdk中的函數式接口的聲明處一般都有@FunctionalInterface
注解,加上這個注解的接口,如果不滿足函數式接口的規范(只有一個抽象方法),編譯器就會報錯。
對于函數式接口,Java8
引入lambda
表達式來進一步簡化匿名內部類的寫法,因此非函數式接口是不能用lambda
表達式的形式來創建接口的實例。
lambda
表達式在許多語言中都有,比如在JavaScript
中是=>
表示的函數寫法,在Java中則是->
。
Lambda表達式的形式:
(參數1,參數2,...) -> {
// 抽象方法實現的代碼塊
...
}
- 如果參數只有1個,則可以省略掉括號
- 如果代碼塊中只有一行代碼,則可以省略掉花括號和代碼塊結尾的分號
- 如果代碼塊中只有一條語句,且該語句為return語句,則可以將return省略
一個比較簡短的lambda表達式長這樣:
a -> a+1
() -> System.out.println("hello world!")
進一步簡化lambda表達式
方法引用的引出是為了簡化代碼,簡化什么代碼呢?答案就是簡化lambda表達式,而且是對于只有一行代碼的lambda表達式。下面來看幾個案例:
- 將一個整型數字轉換成對應的字符串
// 接口
interface A {
String m(Integer i);
}
// 創建A的一個實例,lambda表達式寫法
A a = i -> String.valueOf(i);
a.m(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
采用擦除法,去掉左右兩邊一致的參數表
對象::實例方法名
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;
同樣是擦除法記憶,去掉左右兩邊一致的參數表
類名::實例方法名
lambda表達式中調用lambda形參中第一個參數的某個實例方法,并且lambda形參剩余的n-1個參數作為這個實例方法的實參,并且lambda的方法返回類型要和實例方法的返回類型對應。
str -> str.toLowerCase(); // 對應方法引用寫法:String::toLowerCase
類型::new
類型我們分為基本數據類型和引用類型.
- 基本數據類型
對于普通基本數據類型沒有new的操作,但是創建對應的數組則是通過關鍵字new
來完成,lambda的形參作為數組的長度傳入,比如:
- 引用類型
lambda的形參作為某個類的構造方法的實參,如:
總結
方法引用可使得Java代碼編寫起來更加簡短,所有方法引用的寫法都需要滿足lambda方法的返回值類型與方法引用的返回值類型一致,即:
lambda 方法返回值類型為void ,則方法引用的返回值可以是void或者非void
lambda方法返回值非void,則方法引用的返回值類型要保持相同或者符合里氏置換原則(LSP)