欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 宝鼎售后问题提交 | 后台管理


新闻资讯

MENU

软件开发知识

回调接口改造成函数接口 接下来 次  来源:昆山软开发 时间:2018-02-27

原文出处: 琴水玉

摘要:通过一次并发处理惩罚数据集的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);