摘要:通过一次并发处理惩罚数据集的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>(); for (String key: keys) { result.add(Integer.valueOf(key) % 1000000000); } return result; }
可以写成一行代码:
return keys.stream().map(key -> Integer.valueOf(key) % 1000000000).collect(Collectors.toList());
不外, 写多了, collect(Collectors.toList()) 会大量呈现,占篇幅,并且当 map 里的函数较量巨大时,IDE 有时不能自动补全。留意到这个函数其实就是传一个列表和一个数据处理惩罚函数,因此,可以抽离出一个 StreamUtil ,之前的代码可以写成:
public static <T,R> List<R> map(List<T> data, Function<T, R> mapFunc) { return data.stream().map(mapFunc).collect(Collectors.toList()); // stream replace foreach } return StreamUtil.map(keys, key -> Integer.valueOf(key) % 1000000000);