Java 新特性,使用 Lambdas 表達式作為 Predicates
在 Java 8 之前的代碼使用傳統的 for 循環條件和使用 StringBuilder 逐步構建一個字符串。Java 8 代碼使用 map 實體,映射(轉換)每一個實體變成字符串形式 "key:value" ,并最終使用了 Collector 加入這些查詢片段。這是一個常見的函數式代碼,一個 for 循環將一個對象集合轉換成不同的對象集合,過濾也是可選的,并選擇性地減少單個元素的集合。在函數風格上,map,filter,reduce 等等都是一些通用的模式。你可以總是用條件過濾代替一個 for 循環,減少 Java 8 的 map, filter 和 reduce (collect) 等操作。
而除了 stream API 之外, Java 8 也引入了一些新的 API 方法,它們可以讓某些東西簡單許多,很不錯。例如,假設我們要有下面這樣一個方法來移除一個給定的鍵值的集合中所有的映射項。在示例代碼中, dataCache 是一個 ConcurrentMap ,而 deleteKeys 是我們將要從緩存中移除的鍵值的集合。下面是我遇到過的采用原始方法實現的代碼:
public void deleteFromCache(Set<String> deleteKeys) {
Iterator<Map.Entry<String, Object>> iterator = dataCache.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
if (deleteKeys.contains(entry.getKey())) {
iterator.remove();
}
}
}
現在,你可能會說有更好的方法來做這件事情。比方說對要刪除的鍵值進行迭代,然后使用 Map#remove(Object key) 方法將每一個映射移除。例如:
public void deleteFromCache(Set<String> deleteKeys) {
for (String deleteKey : deleteKeys) {
dataCache.remove(deleteKey);
}
}
盡管功能上是對等的,但代碼中使用 for 循環看起來似乎比在這種情形下使用 Iterator 更加清晰。那我們可以做得更好嗎? Java 8 引入了 removeIf 方法作為一個默認方法,沒有把它放到 Map 中,而是放到了 Collection 接口類中。這一新方法 "將集合中所有滿足給定謂語條件的元素移除", 該描述引用自 Javadocs 文檔。這個方法會接收一個參數,也就是一個 Predicate , 它是在Java8中引入的一個函數式的接口, 而其也因此能被用在 lambda 表達式中。讓我們首先來將其實現_——一個普通的老的匿名內部類,甚至在 Java 8 中,你也可以這樣做。代碼如下:
public void deleteFromCache(Set<String> deleteKeys) {
dataCache.entrySet().removeIf(new Predicate<Map.Entry<String, Object>>() {
@Override
public boolean test(Map.Entry<String, Object> entry) {
return deleteKeys.contains(entry.getKey());
}
});
}
如你所見,我們首先通過 entrySet 方法取得了 map 的 entry 集合,然后在上面調用 removeIf,提供了一個 Predicate 來檢測 deleteKeys 的集合中是否包含這個 entry 的鍵值。如果檢測返回的是 true,entry 就會被移除。因為 Predicate 被加上了 @FunctionalInterface 注解, 因此,根據 Javadoc 的說明 , 它也可以被當做是一個 lambda 表達式,一個方法引用,或者是一個構造器引用。因此,首先讓我們先將這個匿名內部類轉換成一個 lambda 表達式:
public void deleteFromCache(Set<String> deleteKeys) {
dataCache.entrySet().removeIf((Map.Entry<String, Object> entry) ->
deleteKeys.contains(entry.getKey()));
}
在上述代碼中,我們用一個 lambda 表達式替換了匿名類,它會接收一個 Map.Entry 作為參數。不過 Java 8 可以自己對 lambda 表達式的參數類型進行推斷,所以我們可以將明確(且有點啰嗦)的類型聲明去掉了, 剩下的就是下面這樣更加干凈的代碼:
public void deleteFromCache(Set<String> deleteKeys) {
dataCache.entrySet().removeIf(entry -> deleteKeys.contains(entry.getKey()));
}
這段代碼比原來使用 Iterator 的那段看起來好了很多。但如果是把它同使用了一個簡單的 for 循環來遍歷鍵值,然后調用 remove 來移除每一個元素的第二段代碼做比較會如何呢?代碼的行與行之間真的是沒啥不同,因此假定它們在功能上是對等的,那么也許區別僅僅只是風格外表上面的。看上起比較明確的 for 循環是一種傳統的命令式的風格,而 removeIf 則有一種功能上更加強大的意味。如果你查看 Collection 接口中 removeIf 的實際實現,就會發現它實際就是使用的 Iterator ,就跟本文的第一個示例一樣。
所以實際上在功能方面是沒有區別的。不過 removeIf 在理論上可以針對特定類型的集合執行 并行 操作來進行實現, 而也許只會對特定大小的集合才能顯示其并行操作具備優勢。不過這里的示例實際上更多的是關于關注點分離的,例如將決定一個元素是否應該被刪除的業務邏輯中同集合的遍歷邏輯分離。
例如,在需要將一個集合中不同位置的元素刪除的時候,很可能會在不同的地方寫出類似的遍歷循環,其中還摻雜著刪除邏輯。但是 removeIf 方法需要的只是在不同的地方寫刪除邏輯,而且這些邏輯才是真正的業務邏輯。而且,如果將來Java的集合遍歷邏輯有某種改進,比如對大集合進行并行運算時,那么所有使用這個方法的地方將會 自動 獲得這種好處,如果使用顯示的迭代器或循環的則沒辦法實現。
在諸如此類的情況下,我認為責任分離是函數式編程優于命令式編程的一個重要原因。責任分離會產生更好、更清晰的代碼和更方便的代碼精確重用,因為責任都是分別實現的,同時測試也是分離的,也就意味著不僅有更清晰的生產代碼,也有更清晰的測試代碼。所有這些都帶來更易維護的代碼,也就意味著新特性和對已有代碼的改進將會完成的更快,對已有代碼的破壞也會更低。直到本系列下一篇關于Java 8特性和函數式編程的文章之前,請快樂編程!
來自:https://www.oschina.net/translate/towards-more-functional-java-using-lambdas-as-pred