方法引用(Method references)
lambda表達式允許我們定義一個匿名方法,並允許我們以函數式接口的方式使用它。我們也希望能夠在已有的方法上實現同樣的特性。
方法引用和lambda表達式擁有相同的特性(例如,它們都需要一個目標類型,並需要被轉化為函數式接口的實例),不過我們並不需要為方法引用提供方法體,我們可以直接通過方法名稱引用已有方法。
以下面的代碼為例,假設我們要按照name或age為Person數組進行排序:
class Person { private final String name; private final int age; public int getAge() { return age; } public String getName() {return name; } ... } Person[] people = ... Comparator<Person> byName = Comparator.comparing(p -> p.getName()); Arrays.sort(people, byName);
在這里我們可以用方法引用代替lambda表達式:
Comparator<Person> byName = Comparator.comparing(Person::getName);
這里的Person::getName可以被看作為lambda表達式的簡寫形式。盡管方法引用不一定(比如在這個例子里)會把語法變的更緊湊,但它擁有更明確的語義——如果我們想要調用的方法擁有一個名字,我們就可以通過它的名字直接調用它。
因為函數式接口的方法參數對應於隱式方法調用時的參數,所以被引用方法簽名可以通過放寬類型,裝箱以及組織到參數數組中的方式對其參數進行操作,
就像在調用實際方法一樣:
Consumer<Integer> b1 = System::exit; // void exit(int status) Consumer<String[]> b2 = Arrays:sort; // void sort(Object[] a) Consumer<String> b3 = MyProgram::main; // void main(String... args) Runnable r = Myprogram::mapToInt // void main(String... args)
方法引用的種類(Kinds of method references)
方法引用有很多種,它們的語法如下:
- 靜態方法引用:ClassName::methodName
- 實例上的實例方法引用:instanceReference::methodName
- 超類上的實例方法引用:super::methodName
- 類型上的實例方法引用:ClassName::methodName
- 構造方法引用:Class::new
- 數組構造方法引用:TypeName[]::new
對於靜態方法引用,我們需要在類名和方法名之間加入::分隔符,例如Integer::sum。
對於具體對象上的實例方法引用,我們則需要在對象名和方法名之間加入分隔符:
Set<String> knownNames = ...
Predicate<String> isKnown = knownNames::contains;
這里的隱式lambda表達式(也就是實例方法引用)會從knownNames中捕獲String對象,而它的方法體則會通過Set.contains使用該String對象。
有了實例方法引用,在不同函數式接口之間進行類型轉換就變的很方便:
Callable<Path> c = ...
Privileged<Path> a = c::call;
引用任意對象的實例方法則需要在實例方法名稱和其所屬類型名稱間加上分隔符:
Function<String, String> upperfier = String::toUpperCase;
這里的隱式lambda表達式(即String::toUpperCase實例方法引用)有一個String參數,這個參數會被toUpperCase方法使用。
如果類型的實例方法是泛型的,那么我們就需要在::分隔符前提供類型參數,或者(多數情況下)利用目標類型推導出其類型。
需要注意的是,靜態方法引用和類型上的實例方法引用擁有一樣的語法。編譯器會根據實際情況做出決定。
一般我們不需要指定方法引用中的參數類型,因為編譯器往往可以推導出結果,但如果需要我們也可以顯式在::分隔符之前提供參數類型信息。
和靜態方法引用類似,構造方法也可以通過new關鍵字被直接引用:
SocketImplFactory factory = MySocketImpl::new;
- 如果類型擁有多個構造方法,那么我們就會通過目標類型的方法參數來選擇最佳匹配,這里的選擇過程和調用構造方法時的選擇過程是一樣的。
- 如果待實例化的類型是泛型的,那么我們可以在類型名稱之后提供類型參數,否則編譯器則會依照"菱形"構造方法調用時的方式進行推導。
數組的構造方法引用的語法則比較特殊,為了便於理解,你可以假想存在一個接收int參數的數組構造方法。
參考下面的代碼:
IntFunction<int[]> arrayMaker = int[]::new; int[] array = arrayMaker.apply(10) // 創建數組 int[10]