多個域名 指向同一個網(wǎng)站網(wǎng)站搭建費用
Java8實戰(zhàn)-總結(jié)11
- Lambda表達式
- 方法引用
- 管中窺豹
- 如何構(gòu)建方法引用
- 構(gòu)造函數(shù)引用
Lambda表達式
方法引用
方法引用讓你可以重復使用現(xiàn)有的方法定義,并像Lambda
一樣傳遞它們。在一些情況下,比起使用Lambda
表達式,它們似乎更易讀,感覺也更自然。下面就是借助更新的Java 8 API
,用方法引用寫的一個排序的例子:
先前:
3
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
之后(使用方法引用和java.util.Comparator.comparing
):
inventory.sort (comparing(Apple::getWeight));
管中窺豹
為什么應該關心方法引用?方法引用可以被看作僅僅調(diào)用特定方法的Lambda
的一種快捷寫法。它的基本思想是,如果一個Lambda
代表的只是“直接調(diào)用這個方法”,那最好還是用名稱來調(diào)用它,而不是去描述如何調(diào)用它。事實上,方法引用就是讓你根據(jù)已有的方法實現(xiàn)來創(chuàng)建Lambda
表達式。但是,顯式地指明方法的名稱,代碼的可讀性會更好。它是如何工作的呢?當需要使用方法引用時,目標引用放在分隔符::
前,方法的名稱放在后面。例如,Apple::getWeight
就是引用了Apple類中定義的方法getWeight
。請記住,不需要括號,因為沒有實際調(diào)用這個方法。方法引用就是Lambda
表達式(Apple a) -> a.getWeight()
的快捷寫法。下表給出了Java 8
中方法引用的其他一些例子。
可以把方法引用看作針對僅僅涉及單一方法的Lambda的語法糖,因為你表達同樣的事情時要寫的代碼更少了。
如何構(gòu)建方法引用
方法引用主要有三類。
- 指向靜態(tài)方法的方法引用(例如
Integer
的parseInt
方法,寫作Integer::parseInt
)。 - 指向任意類型實例方法的方法引用(例如
String
的length
方法,寫作String::length
)。 - 指向現(xiàn)有對象的實例方法的方法引用(假設有一個局部變量
expensiveTransaction
用于存放Transaction
類型的對象,它支持實例方法getValue
,那么你就可以寫expensiveTransaction::getValue
)。
第二種和第三種方法引用可能乍看起來有點兒暈。類似于string::length
的第二種方法引用的思想就是在引用一個對象的方法,而這個對象本身是Lambda
的一個參數(shù)。例如,Lambda
表達式(String s) -> .toUppeCase()
可以寫作String::toUpperCase
。但第三種方法引用指的是,在Lambda
中調(diào)用一個已經(jīng)存在的外部對象中的方法。例如,Lambda
表達式()->expensiveTransaction.getValue()
可以寫作expensiveTransaction::getValue
。依照一些簡單的方子,就可以將Lambda
表達式重構(gòu)為等價的方法引用,如下圖所示:
請注意,還有針對構(gòu)造函數(shù)、數(shù)組構(gòu)造函數(shù)和父類調(diào)用(super-call
)的一些特殊形式的方法引用。舉一個方法引用的具體例子吧。比方說想要對一個字符串的List
排序,忽略大小寫。List
的sort
方法需要一個Comparator
作為參數(shù)。在前面看到,Comparator
描述了一個具有(T, T)->int
簽名的函數(shù)描述符。可以利用string
類中的compareToIgnoreCase
方法來定義一個Lambda
表達式(注意compareToIgnoreCase
是String
類中預先定義的)。
List<String> str = Arrays.asList("a","b","A","B");
str.sort((s1, s2)-> s1.compareToIgnorecase(s2));
Lambda
表達式的簽名與Comparator
的函數(shù)描述符兼容。利用前面所述的方法,這個例子可以用方法引用改寫成下面的樣子:
List<String> str = Arrays.asList("a","b","A","B");
str.sort(String::compareToIgnoreCase);
請注意,編譯器會進行一種與Lambda
表達式類似的類型檢查過程,來確定對于給定的函數(shù)式接口,這個方法引用是否有效:方法引用的簽名必須和上下文類型匹配。
測驗:方法引用
下列Lambda表達式的等效方法引用是什么?
(1) Punction<String, Integer> stringToInteger = (String s) -> Integer.parseInt(s);(2)BiPredicatecList<String>, String> contains =(list, element) -> list.contains(element);答案如下。
(1)這個Lambda表達式將其參數(shù)傳給了Integer的靜態(tài)方法parseInt。這種方法接受一個需要解析的String,并返回一個Integer。因此,可以使用上圖中的辦法①(Lambda表達式調(diào)用靜態(tài)方法)來重寫Lambda表達式,如下所示:
Function<String, Integer> stringToInteger = Integer::parseInt;
(2)這個Lambda使用其第一個參數(shù),調(diào)用其contains方法。由于第一個參數(shù)是List類型的,可以使用上圖中的辦法②,如下所示:
BiPredicate<List<String>, String> contains = List::contains;
這是因為,目標類型描述的函數(shù)描述符是(List<String>, String) -> boolean,而List::contains可以被解包成這個函數(shù)描述符。
到目前為止,只展示了如何利用現(xiàn)有的方法實現(xiàn)和如何創(chuàng)建方法引用。但是也可以對類的構(gòu)造函數(shù)做類似的事情。
構(gòu)造函數(shù)引用
對于一個現(xiàn)有構(gòu)造函數(shù),可以利用它的名稱和關鍵字new
來創(chuàng)建它的一個引用:
ClassName::new
。它的功能與指向靜態(tài)方法的引用類似。例如,假設有一個構(gòu)造函數(shù)沒有參數(shù)。
它適合Supplier
的簽名() -> Apple
。可以這樣做:
//構(gòu)造函數(shù)引用指向默認的Apple()構(gòu)造函數(shù)
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
這就等價于:
//調(diào)用Supplier的get方法將產(chǎn)生一個新的Apple
//利用默認構(gòu)造函數(shù)創(chuàng)建Apple的Lambda表達式
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();
如果構(gòu)造函數(shù)的簽名是Apple(Integer weight)
,那么它就適合Function
接口的簽名,于是可以這樣寫:
//指向Apple(Integer weight)的構(gòu)造函數(shù)引用
Function<Integer, Apple> c2 = Apple::new;
//調(diào)用該Function函數(shù)的apply方法,并給出要求的重量,將產(chǎn)生一個Apple
Apple a2 = c2.apply(110);
這就等價于:
用要求的重量創(chuàng)建一
個Apple的Lambda表
//用要求的重量創(chuàng)建一個Apple的Lambda表達式
FunctioncInteger,Apple> c2 =(weight)-> new Apple(weight);
//調(diào)用該Punction函數(shù)的apply方法,并給出要求的重量,將產(chǎn)生一個新的Apple對象
Apple a2 = c2.apply(110);
在下面的代碼中,一個由Integer
構(gòu)成的List
中的每個元素都通過前面定義的類似的map
方法傳遞給了Apple
的構(gòu)造函數(shù),得到了一個具有不同重量蘋果的List
:
//將構(gòu)造函數(shù)引用傳遞給map方法List<Integer> weights = Arrays.asList(7,3,4,10);List<Apple> apples = map(weights, Apple::new);public static List<Apple> map(List<Integer> list, Function<Integer, Apple> f) {List<Apple> result = new ArrayList<>();for(Integer e: list) {result.add(f.apply(e));}return result;}
如果有一個具有兩個參數(shù)的構(gòu)造函數(shù)Apple(String color, Integer weight)
,那么它就適合BiFunction
接口的簽名,于是可以這樣寫:
//指向Apple(String color,Integer weight)的構(gòu)造函數(shù)引用
BiPunction<String, Integer, Apple> c3 = Apple::new;
//調(diào)用該BiFunction函數(shù)的apply方法,并給出要求的顏色和重量,將產(chǎn)生一個新的Apple對象
Apple c3 = c3.apply("green", 110);
這就等價于:
//用要求的顏色和重量創(chuàng)建一個Apple的Lambda表達式
BiPunction<String, Integer, Apple> c3 = (color, weight) -> new Apple(color, weight);//調(diào)用該BiPunction函數(shù)的apply方法,并給出要求的顏色和重量,將產(chǎn)生一個新的Apple對象
Apple c3 = c3.apply("green", 110);
不將構(gòu)造函數(shù)實例化卻能夠引用它,這個功能有一些有趣的應用。例如,可以使用Map
來將構(gòu)造函數(shù)映射到字符串值。創(chuàng)建一個giveMeFruit
方法,給它一個String
和一個Integer
,它就可以創(chuàng)建出不同重量的各種水果:
static Map<String, Function<Integer, Fruit>> map = new HashMap<>();
static {map.put("apple", Apple::new);map.put("orange", Orange::new);// etc...
}//用map得到了一個Function<Integer, Fruit>
public static Fruit giveMeFruit(String fruit, Integer weight) {//用Integer類型的weight參數(shù)調(diào)用Function的apply()方法將提供所要求的Fruitreturn map.get(fruit.toLowercase()).apply(weight);
}
測驗:構(gòu)造函數(shù)引用
已經(jīng)看到了如何將有零個、一個、兩個參數(shù)的構(gòu)造函數(shù)轉(zhuǎn)變?yōu)闃?gòu)造函數(shù)引用。那要怎么樣才能對具有三個參數(shù)的構(gòu)造函數(shù),比如Color(int,int,int),使用構(gòu)造函數(shù)引用呢?答案:構(gòu)造函數(shù)引用的語法是ClassName::new,那么在這個例子里面就是Color::new。但是需要與構(gòu)造函數(shù)引用的簽名匹配的函數(shù)式接口。
但是語言本身并沒有提供這樣的函數(shù)式接口,可以自己創(chuàng)建一個:
public interface TriFunction<T,U, V, R> {R apply(T t,U u,V v);
}
現(xiàn)在可以像下面這樣使用構(gòu)造函數(shù)引用了:
TriPunction<Integer, Integer, Integer, Color> colorFactory = Color::new;