设为首页 加入收藏

TOP

Java8 函数式编程探秘(一)
2018-01-30 12:42:56 】 浏览:912
Tags:Java8 函数 编程 探秘

引子

将行为作为数据传递

怎样在一行代码里同时计算一个列表的和、最大值、最小值、平均值、元素个数、奇偶分组、指数、排序呢?

答案是思维反转!将行为作为数据传递。 文艺青年的代码如下所示:

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 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)));
   }
}

呃,有点卖弄小聪明。 不过要是能将行为作为数据自由传递和施加于数据集产生结果,那么其代码表达能力将如庄子之言,恣意潇洒而无所极限。病毒,不就是将行为匿身于数据中的一种表现么?

行为就是数据。

Java8函数框架解读

函数编程的最直接的表现,莫过于将函数作为数据自由传递,结合泛型推导能力,使代码表达能力获得飞一般的提升。那么,Java8是怎么支持函数编程的呢?主要有三个核心概念:

  • 函数接口(Function)
  • 流(Stream)
  • 聚合器(Collector)

函数接口

关于函数接口,需要记住的就是两件事:

  • 函数接口是行为的抽象
  • 函数接口是数据转换器

最直接的支持就是 java.util.Function 包。定义了四个最基础的函数接口:

  • Supplier<T>: 数据提供器,可以提供 T 类型对象;无参的构造器,提供了 get 方法;
  • Function<T,R>: 数据转换器,接收一个 T 类型的对象,返回一个 R类型的对象; 单参数单返回值的行为接口;提供了 apply, compose, andThen, identity 方法;
  • Consumer<T>: 数据消费器, 接收一个 T类型的对象,无返回值,通常用于设置T对象的值; 单参数无返回值的行为接口;提供了 accept, andThen 方法;
  • Predicate<T>: 条件测试器,接收一个 T 类型的对象,返回布尔值,通常用于传递条件函数; 单参数布尔值的条件性接口。提供了 test (条件测试) , and-or- negate(与或非) 方法。

其中, compose, andThen, and, or, negate 用来组合函数接口而得到更强大的函数接口。

其它的函数接口都是通过这四个扩展而来。

  • 在参数个数上扩展: 比如接收双参数的,有 Bi 前缀, 比如 BiConsumer<T,U>, BiFunction<T,U,R> ;
  • 在类型上扩展: 比如接收原子类型参数的,有 [Int|Double|Long][Function|Consumer|Supplier|Predicate]
  • 特殊常用的变形: 比如 BinaryOperator , 是同类型的双参数 BiFunction<T,T,T> ,二元操作符 ; UnaryOperator 是 Function<T,T> 一元操作符。

那么,这些函数接口可以接收哪些值呢?

  • 类/对象的静态方法引用、实例方法引用。引用符号为双冒号 ::
  • 类的构造器引用,比如 Class::new
  • lambda表达式

在博文“使用函数接口和枚举实现配置式编程(Java与Scala实现)”, “精练代码:一次Java函数式编程的重构之旅” 给出了基本的例子。后面还有更多例子。重在自己练习和尝试。

聚合器

先说聚合器。每一个流式计算的末尾总有一个类似 collect(Collectors.toList()) 的方法调用。collect 是 Stream 的方法,而参数则是聚合器Collector。已有的聚合器定义在Collectors 的静态方法里。 那么这个聚合器是怎么实现的呢?

Reduce

大部分聚合器都是基于 Reduce 操作实现的。 Reduce ,名曰推导,含有三个要素: 初始值 init, 二元操作符 BinaryOperator, 以及一个用于聚合结果的数据源S。

Reduce 的算法如下:

STEP1: 初始化结果 R = init ;

STEP2: 每次从 S 中取出一个值 v,通过二元操作符施加到 R 和 v ,产生一个新值赋给 R = BinaryOperator(R, v);重复 STEP2, 直到 S 中没有值可取为止。

比如一个列表求和,Sum([1,2,3]) , 那么定义一个初始值 0 以及一个二元加法操作 BO = a + b ,通过三步完成 Reduce 操作:step1: R = 0; step2: v=1, R = 0+v = 1; step2: v=2, R = 1 + v = 3 ; step3: v = 3, R = 3 + v = 6。

四要素

一个聚合器的实现,通常需要提供四要素:

  • 一个结果容器的初始值提供器 supplier ;
  • 一个用于将每次二元操作的中间结果与结果容器的值进行操作并重新设置结果容器的累积器 accumulator ;
  • 一个用于对Stream元素和中间结果进行操作的二元操作符 combiner ;
  • 一个用于对结果容器进行最终聚合的转换器 finisher(可选) 。

Collectors.CollectorImpl 的实现展示了这一点:

static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
        private final Supplier<A> supplier;
        private final BiConsumer<A, T> accumulator;
        private final BinaryOperator<A> combiner;
        private final Function<A, R> finisher
首页 上一页 1 2 3 4 5 6 7 下一页 尾页 1/9/9
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇通向架构师的道路(第六天)之漫.. 下一篇JUnit 源码解析

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目