函数式编程的益处


更精练的代码

函数编程的一大益处,是用更精练的代码表达常用数据处理模式。函数接口能够轻易地实现模板方法模式,只要将不确定的业务逻辑抽象成函数接口,然后传入不同的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 函数式编程(下)相关推荐

  1. Java 8 函数式编程学习笔记

    Java 8 函数式编程学习笔记 @(JAVASE)[java8, 函数式编程, lambda] Java 8 函数式编程学习笔记 参考内容 Java 8中重要的函数接口 扩展函数接口 常用的流操作 ...

  2. 【Java】函数式编程学习笔记——Stream流

    学习视频:https://www.bilibili.com/video/BV1Gh41187uR?p=1 (1)[Java]函数式编程学习笔记--Lambda表达式 (2)[Java]函数式编程学习笔 ...

  3. 阅读笔记–Java 8函数式编程,建议看书,作者高屋建瓴

    阅读笔记–Java 8函数式编程 书籍代码 为什么需要再次修改Java 1996年1月,Java1.0发布,商业发展需要更复杂的应用,跑在功能强大的多核CPU机器上.带有高效运行时编译器的Java虚拟 ...

  4. Java 代码写的又臭又长,还不会用 Java Stream 函数式编程?

    点击上方"猿芯",选择"设为星标" 后台回复"1024",有份惊喜送给面试的你 原文 https://www.cnblogs.com/Car ...

  5. java8 函数式编程_如何使用Java 8函数式编程生成字母序列

    java8 函数式编程 我偶然发现了用户" mip"一个有趣的堆栈溢出问题 . 问题是: 我正在寻找一种生成字母序列的方法: A, B, C, ..., Z, AA, AB, AC ...

  6. 如何使用Java 8函数式编程生成字母序列

    我偶然发现了用户" mip"一个有趣的堆栈溢出问题 . 问题是: 我正在寻找一种生成字母序列的方法: A, B, C, ..., Z, AA, AB, AC, ..., ZZ. 可 ...

  7. Java 8th 函数式编程:lambda 表达式

    Lambda 表达式是 java 8th 给我们带来的几个重量级新特性之一,借用 lambda 表达式可以让我们的程序设计更加简洁.最近新的项目摒弃了 6th 版本,全面基于 8th 进行开发,本文将 ...

  8. java supplier_Java 函数式编程

    前些年 Scala 大肆流行,打出来 Java 颠覆者的旗号,究其底气来源,无非是函数式和面向对象的"完美结合",各式各样的"语法糖",但其过高的学习门槛,又给 ...

  9. JAVA 自定义函数式编程

    PS: 因想提取重复代码,所以寻求方案,最终确认使用函数式编程.但百度查下大部分写的是内置类用新版的函数式接口怎么实现(Consumer  Function Predicate): 1. 创建一个sp ...

最新文章

  1. 7000p可以加装固态吗_解惑丨地暖不热加装循环泵可以吗?
  2. jsp 实栗 jsp + jdbc 登录
  3. lamp 环境搭建论坛
  4. σ(゚∀゚ ∬オレの性格だ
  5. android如何添加gif,Android加载Gif和ImageView的通用解决方案:android-gif-drawable(1)...
  6. C#实现局域网UDP广播--
  7. 迭代器 java_面试必备(含答案):30 个 Java 高频面试问题
  8. linux shell脚本EOF妙用
  9. 在系统编程ISP及在应用编程IAP
  10. python词频统计并按词频排序
  11. 团队作业-Beta冲刺(3)
  12. Java程序性能优化——性能调优层次
  13. 前端---js中onmouseover和onmouseout事件
  14. 短期学习就能月薪过万?IT培训套路揭秘,教育机构宣传是真是假
  15. 5GC PDU Session Establishment PDU会话建立流程
  16. Java判断用户是通过pc,还是安卓还是ios,还是微信客户端访问
  17. 多年经验,怎么做一个优秀的售后工程师
  18. 数据库的语言分类--联通研究院面经
  19. 代码随想录训练营day55
  20. 序列化和反序列化之json和pickle模块

热门文章

  1. [译文]Domain Driven Design Reference(五)—— 为战略设计的上下文映
  2. spring的Autowired和@Resource的区别是什么
  3. topcoder srm 711 div1 -3
  4. 用C语言做的 一个整人的小程序
  5. 通知公告阅读日志构建说明
  6. 利用 python 的各种开源工具自制浏览器
  7. python3.4 使用pymysql 连接mysql数据库
  8. 数据与程序分离——程序中那些表的事儿
  9. 有限元基础: Jacobian 矩阵和高斯积分
  10. windows下安装emscripten