原文出处:
琴水玉
摘要:通过一次并发处理数据集的Java代码重构之旅,展示函数式编程如何使得代码更加精练。
难度:中级
基础知识
在开始之前,了解“高阶函数”和“泛型”这两个概念是必要的。
高阶函数就是接收函数参数的函数,能够根据传入的函数参数调节自己的行为。类似C语言中接收函数指针的函数。最经典的就是接收排序比较函数的排序函数。高阶函数不神秘哦!在Java8之前,就是那些可以接收回调接口作为参数的方法;在本文中,那么接收 Function, Consumer, Supplier 作为参数的函数都是高阶函数。高阶函数使得函数的能力更加灵活多变。
泛型是能够接纳多种类型作为参数进行处理的能力。很多函数的功能并不限于某一种具体的类型,比如快速排序,不仅可以用于整型,也可以用于字符串,甚至可用于对象。泛型使得函数在类型处理上更加灵活。
高阶函数和泛型两个特点结合起来,可使得函数具备强大的抽象表达能力。
重构前
基本代码如下。主要用途是根据具体的业务数据获取接口 IGetBizData ,并发地获取指定Keys值对应的业务数据集。
package zzz.study.function.refactor.before; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import zzz.study.function.refactor.TaskUtil; /** * Created by shuqin on 17/6/23. */ public class ConcurrentDataHandlerFrame { public static void main(String[] args) { List<Integer> allData = getAllData(getKeys(), new GetTradeData()); System.out.println(allData); } public static List<String> getKeys() { List<String> keys = new ArrayList<String>(); for (int i=0; i< 20000; i++) { keys.add(String.valueOf(i)); } return keys; } /** * 获取所有业务数据 */ public static <T> List<T> getAllData(List<String> allKeys, final IGetBizData iGetBizData) { List<String> parts = TaskUtil.divide(allKeys.size(), 1000); System.out.println(parts); ExecutorService executor = Executors.newFixedThreadPool(parts.size()); CompletionService<List<T>> completionService = new ExecutorCompletionService<List<T>>(executor); for (String part: parts) { int start = Integer.parseInt(part.split(":")[0]); int end = Integer.parseInt(part.split(":")[1]); if (end > allKeys.size()) { end = allKeys.size(); } final List<String> tmpRowkeyList = allKeys.subList(start, end); completionService.submit(new Callable<List<T>>() { public List<T> call() throws Exception { return iGetBizData.getData(tmpRowkeyList); } }); } List<T> result = new ArrayList<T>(); for (int i=0; i< parts.size(); i++) { try { result.addAll(completionService.take().get()); } catch (Exception e) { e.printStackTrace(); } } executor.shutdown(); return result; } } /** 业务数据接口 */ interface IGetBizData<T> { List<T> getData(List<String> keys); } /** 获取业务数据具体实现 */ class GetTradeData implements IGetBizData<Integer> { public List<Integer> getData(List<String> keys) { // maybe xxxService.getData(keys); List<Integer> result = new ArrayList<Integer>(); for (String key: keys) { result.add(Integer.valueOf(key) % 1000000000); } return result; } }
代码本身写得不坏,没有拗口的地方,读起来也比较流畅。美中不足的是,不够通用化。 心急的读者可以看看最后面重构后的代码。这里还是从重构过程开始。
重构过程
从小处着手
如果面对一大块代码不知如何下手,那么就从小处着手,先动起来。 对于如下代码,了解 Java8 Stream api 的同学肯定知道怎么做了:
public List<Integer> getData(List<String> keys) { // maybe xxxService.getData(keys); List<Integer> result = new ArrayList<Integer>(); f