深度探秘 Java 8 函数式编程(下)
函數式編程的益處
更精練的代碼
函數編程的一大益處,是用更精練的代碼表達常用數據處理模式。函數接口能夠輕易地實現模板方法模式,只要將不確定的業務邏輯抽象成函數接口,然后傳入不同的lambda表達式即可。博文“精練代碼:一次Java函數式編程的重構之旅”?展示了如何使用函數式編程來重構常見代碼,萃取更多可復用的代碼模式。
這里給出一個列表分組的例子。實際應用常常需要將一個列表 List[T] 轉換為一個 Map[K, List[T]] , 其中 K 是通過某個函數來實現的。 看下面一段代碼:
public?static?Map<String,?List<OneRecord>>?buildRecordMap(List<OneRecord>?records,?List<String>?colKeys)?{Map<String,?List<OneRecord>>?recordMap?=?new?HashMap<>();records.forEach(record?->?{String?recordKey?=?buildRecordKey(record.getFieldValues(),?colKeys);if?(recordMap.get(recordKey)?==?null)?{recordMap.put(recordKey,?new?ArrayList<OneRecord>());}recordMap.get(recordKey).add(record);});return?recordMap;}可以使用 Collectors.groupingby 來簡潔地實現:
public?static?Map<String,?List<OneRecord>>?buildRecordMapBrief(List<OneRecord>?records,?List<String>?colKeys)?{return?records.stream().collect(Collectors.groupingBy(record?->?buildRecordKey(record.getFieldValues(),?colKeys)));}很多常用數據處理算法,都可以使用函數式編程的流式計算簡潔表達。
更通用的代碼
使用函數接口,結合泛型,很容易用精練的代碼,寫出非常通用的工具方法。 實際應用中,常常會有這樣的需求: 有兩個對象列表srcList和destList,兩個對象類型的某個字段K具有相同的值;需要根據這個相同的值合并對應的兩個對象的信息。
這里給出了一個列表合并函數,可以將一個對象列表合并到指定的對象列表中。實現是: 先將待合并的列表srcList根據key值函數keyFunc構建起srcMap,然后遍歷dest列表的對象R,將待合并的信息srcMap[key]及T通過合并函數mergeFunc生成的新對象R添加到最終結果列表。
??public?static?<K,R>?List<R>?mergeList(List<R>?srcList,?List<R>?destList?,Function<R,K>?keyFunc,BinaryOperator<R>?mergeFunc)?{return?mergeList(srcList,?destList,?keyFunc,?keyFunc,?mergeFunc);}public?static?<T,S,K,R>?List<R>?mergeList(List<S>?srcList,?List<T>?destList?,Function<S,K>?skeyFunc,?Function<T,K>?dkeyFunc,BiFunction<S,T,R>?mergeFunc)?{Map<K,S>?srcMap?=?srcList.stream().collect(Collectors.toMap(skeyFunc,?s?->?s,?(k1,k2)?->?k1));return?destList.stream().map(dest?->?{K?key?=?dkeyFunc.apply(dest);S?src?=?srcMap.get(key);return?mergeFunc.apply(src,?dest);}).collect(Collectors.toList());}更可測的代碼
使用函數接口可以方便地隔離外部依賴,使得類和對象的方法更純粹、更具可測性。博文“使用Java函數接口及lambda表達式隔離和模擬外部依賴更容易滴單測”,“改善代碼可測性的若干技巧”集中討論了如何使用函數接口提升代碼的可單測性。
組合的力量
函數編程的強大威力,在于將函數接口組合起來,構建更強大更具有通用性的實用工具方法。超越類型,超越操作與數據的邊界。
前面提到,函數接口就是數據轉換器。比如Function?就是“將T對象轉換成R對象的行為或數據轉換器”。對于實際工程應用的普通級函數編程足夠了。不過,要玩轉函數接口,就要升級下認識。 比如 Function<bifunction, Function> 該怎么理解呢?這是“一個一元函數g(h(s,q)) ,參數指定的二元函數h(s,q)應用于指定的兩個參數S,Q,得到一個一元函數f(t),這個函數接收一個T對象,返回一個R對象”。 如下代碼所示:</bifunction
??public?static?<T,S,Q,R>?Function<BiFunction<S,Q,R>,?Function<T,R>>?op(Function<T,S>?funcx,?Function<T,Q>?funcy)?{return?opFunc?->?aT?->?opFunc.apply(funcx.apply(aT),?funcy.apply(aT));}System.out.println(op(x->?x.toString().length(),?y->?y+",world").apply((x,y)?->?x+"?"?+y).apply("hello"));實現的是 h(t) = h(funx(t), funy(t)) ,h(x,y) 是一個雙參數函數。
“Java函數接口實現函數組合及裝飾器模式”?展示了如何使用極少量的代碼實現裝飾器模式,將簡單的函數接口組合成更強大功能的復合函數接口。
來看上面的?public static <T,S,K,R> List<R> mergeList(List<S> srcList, List<T> destList , Function<S,K> skeyFunc, Function<T,K> dkeyFunc,BiFunction<S,T,R> mergeFunc)?, 通用性雖好,可是有5個參數,有點丑。怎么改造下呢? 看實現,主要包含兩步:1. 將待合并列表轉化為 srcMap: map; 2. 使用指定的函數 dKeyFunc, mergeFunc 作用于destList和srcMap,得到最終結果。可以改寫代碼如下:
public?static?<T,S,K,R>?List<R>?mergeList(List<S>?srcList,?List<T>?destList?,Function<S,K>?skeyFunc,?Function<T,K>?dkeyFunc,BiFunction<S,T,R>?mergeFunc)?{return?join(destList,?mapKey(srcList,?skeyFunc)).apply(dkeyFunc,?(BiFunction)?mergeFunc);}public?static?<T,K>?Map<K,T>?mapKey(List<T>?list,?Function<T,K>?keyFunc)?{return?list.stream().collect(Collectors.toMap(keyFunc,?t?->?t,?(k1,k2)?->?k1));}public?static?<T,S,K,R>?BiFunction<Function<T,K>,?BiFunction<S,T,R>,?List<R>>?join(List<T>?destList,?Map<K,S>?srcMap)?{return?(dkeyFunc,mergeFunc)?->?destList.stream().map(dest?->?{K?key?=?dkeyFunc.apply(dest);S?src?=?srcMap.get(key);return?mergeFunc.apply(src,?dest);}).collect(Collectors.toList());}System.out.println(mergeList(Arrays.asList(1,2),?Arrays.asList("an",?"a"),?s->?s,?t->?t.toString().length(),?(s,t)?->?s+t));mapKey 是一個通用函數,用于將一個 list 按照指定的 keyFunc 轉成一個 Map; join 函數接受一個 list 和待合并的 srcMap, 返回一個二元函數,該函數使用指定的 dkeyFunc 和 mergeFunc 來合并指定數據得到最終的結果列表。這可稱之為“延遲指定行為”。現在, mapKey 和 join 都是通用性函數。Amazing !
?
Java8泛型
在Java8函數式框架的解讀中,可以明顯看到,泛型無處不在。Java8的泛型推導能力也有很大的增強。可以說,如果沒有強大的泛型推導支撐,函數接口的威力將會大打折扣。
完整代碼示例
package?zzz.study.function;import?java.util.Arrays; import?java.util.List; import?java.util.Map; import?java.util.function.BiFunction; import?java.util.function.BinaryOperator; import?java.util.function.Function; import?java.util.function.Supplier; import?java.util.stream.Collectors;/***?Created?by?shuqin?on?17/12/3.*/ public?class?FunctionUtil?{public?static?<T,R>?List<R>?multiGetResult(List<Function<List<T>,?R>>?functions,?List<T>?list)?{return?functions.stream().map(f?->?f.apply(list)).collect(Collectors.toList());}public?static?<K,R>?List<R>?mergeList(List<R>?srcList,?List<R>?destList?,Function<R,K>?keyFunc,BinaryOperator<R>?mergeFunc)?{return?mergeList(srcList,?destList,?keyFunc,?keyFunc,?mergeFunc);}public?static?<T,S,K,R>?List<R>?mergeList(List<S>?srcList,?List<T>?destList?,Function<S,K>?skeyFunc,?Function<T,K>?dkeyFunc,BiFunction<S,T,R>?mergeFunc)?{return?join(destList,?mapKey(srcList,?skeyFunc)).apply(dkeyFunc,?(BiFunction)?mergeFunc);}public?static?<T,K>?Map<K,T>?mapKey(List<T>?list,?Function<T,K>?keyFunc)?{return?list.stream().collect(Collectors.toMap(keyFunc,?t?->?t,?(k1,k2)?->?k1));}public?static?<T,S,K,R>?BiFunction<Function<T,K>,?BiFunction<S,T,R>,?List<R>>?join(List<T>?destList,?Map<K,S>?srcMap)?{return?(dkeyFunc,mergeFunc)?->?destList.stream().map(dest?->?{K?key?=?dkeyFunc.apply(dest);S?src?=?srcMap.get(key);return?mergeFunc.apply(src,?dest);}).collect(Collectors.toList());}/**?對給定的值?x,y?應用指定的二元操作函數?*/public?static?<T,S,R>?Function<BiFunction<T,S,R>,?R>?op(T?x,?S?y)?{return?opFunc?->?opFunc.apply(x,?y);}/**?將兩個函數使用組合成一個函數,這個函數接受一個二元操作函數?*/public?static?<T,S,Q,R>?Function<BiFunction<S,Q,R>,?R>?op(Function<T,S>?funcx,?Function<T,Q>?funcy,?T?x)?{return?opFunc?->?opFunc.apply(funcx.apply(x),?funcy.apply(x));}public?static?<T,S,Q,R>?Function<BiFunction<S,Q,R>,?Function<T,R>>?op(Function<T,S>?funcx,?Function<T,Q>?funcy)?{return?opFunc?->?aT?->?opFunc.apply(funcx.apply(aT),?funcy.apply(aT));}/**?將兩個函數組合成一個疊加函數,?compose(f,g)?=?f(g)?*/public?static?<T>?Function<T,?T>?compose(Function<T,T>?funcx,?Function<T,T>?funcy)?{return?x?->?funcx.apply(funcy.apply(x));}/**?將若干個函數組合成一個疊加函數,?compose(f1,f2,...fn)?=?f1(f2(...(fn)))?*/public?static?<T>?Function<T,?T>?compose(Function<T,T>...?extraFuncs)?{if?(extraFuncs?==?null?||?extraFuncs.length?==?0)?{return?x->x;}return?x?->?Arrays.stream(extraFuncs).reduce(y->y,?FunctionUtil::compose).apply(x);}public?static?void?main(String[]?args)?{System.out.println(multiGetResult(Arrays.asList(list?->?list.stream().collect(Collectors.summarizingInt(x->x)),list?->?list.stream().filter(x?->?x?<?50).sorted().collect(Collectors.toList()),list?->?list.stream().collect(Collectors.groupingBy(x->(x%2==0??"even":?"odd"))),list?->?list.stream().sorted().collect(Collectors.toList()),list?->?list.stream().sorted().map(Math::sqrt).collect(Collectors.toMap(x->x,?y->Math.pow(2,y)))),Arrays.asList(64,49,25,16,9,4,1,81,36)));List<Integer>?list?=?Arrays.asList(1,2,3,4,5);Supplier<Map<Integer,Integer>>?mapSupplier?=?()?->?list.stream().collect(Collectors.toMap(x->x,?y->?y?*?y));Map<Integer,?Integer>?mapValueAdd?=?list.stream().collect(Collectors.toMap(x->x,?y->y,?(v1,v2)?->?v1+v2,?mapSupplier));System.out.println(mapValueAdd);List<Integer>?nums?=?Arrays.asList(Arrays.asList(1,2,3),?Arrays.asList(1,4,9),?Arrays.asList(1,8,27)).stream().flatMap(x?->?x.stream()).collect(Collectors.toList());System.out.println(nums);List<Integer>?fibo?=?Arrays.asList(1,2,3,4,5,6,7,8,9,10).stream().collect(new?FiboCollector());System.out.println(fibo);System.out.println(op(new?Integer(3),?Integer.valueOf(3)).apply((x,y)?->?x.equals(y.toString())));System.out.println(op(x->?x.length(),?y->?y+",world",?"hello").apply((x,y)?->?x+"?"?+y));System.out.println(op(x->?x,?y->?y+",world").apply((x,y)?->?x+"?"?+y).apply("hello"));System.out.println(op(x->?x.toString().length(),?y->?y+",world").apply((x,y)?->?x+"?"?+y).apply("hello"));System.out.println(mergeList(Arrays.asList(1,2),?Arrays.asList("an",?"a"),s->?s,?t->?t.toString().length(),?(s,t)?->?s+t));}}?
小結
本文深入學習了Java8函數式編程框架:Function&Stream&Collector,并展示了函數式編程在實際應用中所帶來的諸多益處。函數式編程是一把大鋒若鈍的奇劍。基于函數接口編程,將函數作為數據自由傳遞,結合泛型推導能力,可編寫出精練、通用、易測的代碼,使代碼表達能力獲得飛一般的提升。
總結
以上是生活随笔為你收集整理的深度探秘 Java 8 函数式编程(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一篇文章带你飞,轻松弄懂 CDN 技术原
- 下一篇: 干货 | 解决分布式场景下数据一致性问题