当然这篇文章并不时髦,但是我希望记录一些干货,主要涉及常用的 函数式接口、Stream,Collector接口及其辅助类、Lambda、异常处理。
1、函数式接口
然后官方文档比较重要的地方我截取了一下
JDK提供了常见的函数式接口,这里只列出最原始的接口,还有一些接口和这些类似或者是基于这些的。
Consumer<T>:此接口接收一个参数不返回值。顾名思义,传进来的T会被消费,不会返回任何东西。另外接口中有一个andThen方法,这是一个默认方法,他会执行当前对象的accept方法然后执行参数的accept方法。
Function<T, R>:消费T,然后返回R,比如Stream的map操作,rxjava的map操作都是这个原理。同样接口中有两个默认方法,andThen,先执行当前方法的apply,返回的结果作为参数的输入,compose方法相反。具体细节建议看源码。
Predicate<T>:消费T,返回布尔值,另外其接口中的默认方法比较简单,建议读一下源码。
Supplier<T>:不接收参数,返回T
BiFunction<T, U, R>:Function的升级版本,接收两个参数返回一个结果
BinaryOperator<T>:BiFunction的特例,接收两个参数全是T,返回也是T
基于这些接口和这些接口的扩展,可以实现一种跨越,典型的例子是jdk8之前,我们写的程序都是命令式的,例如我们要实现加减乘除的运算操作,可以定义四个方法,或者定义一个方法,然后传递接口,那就是所谓的策略模式。但是这样做太复杂。
public int add(int a, int b) {
return a + b;
}
。。。定义四个方法 。。。或者
public int compute(Computor co, int a, int b) { // 策略接口
return co.compute(a, b);
}
这种方式如果使用内部类,会有异常抛不出去的问题,如果单独定义类,就会产生很多Java文件。然而Lambda表达式可以很好的解决。总之一句话,函数式编程传递的参数还是那个pass by value,但是他有更深的实际意义,它传递的是一种行为。我们可以理解为处处传递策略。
2. Stream Collector
Stream的常用方法:filter(Predicate<T> predicate) 上面的函数式接口派上用场了,过滤还是不过滤呢,用户知道,只需要给我一个接口,剩下的就不管了。
map(Function<T, R> mapper):映射操作,至于怎么映射,用户知道,我关心的只是转换这个动作
flatMap(Function<T, ? extends Stream<? extends R>> mapper):这个方法的本质是map的一个特例,经过处理的T用户去处理,但是要求用户处理过后的T一定转换成Stream对象。还是只关心转换的这个行为,不考虑细节。
另外像reduce(归纳)、distinct(去重)、sorted(排序,默认自然顺序)、peek(偷看,实际上就是对流中当前的元素进行Consumer,不会对其造成影响)、limit(限制流的执行次数)、skip(从当前流中的位置跳过几个)、forEach、collect、count、parallel(并行流,基于fork-join)、sequential(串行流)
collect方法单独说一下,这是核心中的核心,无敌的操作。
本质上就是Collector接口在背后进行一系列的骚操作:
我推荐大家去把Collector接口的所有注释都读一遍,这里只截取重点:
Collector<T, A, R>
Supplier<A> supplier();
supplier方法就是用户自定义的中间结果提供者
BiConsumer<A, T> accumulator();
把T累加到A中
BinaryOperator<A> combiner();
把上次的A合并到新的A中,并且返回新的A
Function<A, R> finisher();
把A转换成R,返回R
Set<>Characteristics characteristics(); // 下面具体解释
Collector特征:
enum Characteristics {
CONCURRENT,
UNORDERED,
IDENTITY_FINISH
}
更详细请查看demo中的自定义Collector接口的代码
如果理解好此接口,那么辅助类Collectors的那些操作都很好理解了,我们可以任意的定义特别复杂的收集器。
建议大家阅读整个Collectors的源码,理解好groupingBy方法的源码。
3.Lambda
前面已经说过了,函数式接口的实例可以被Lambda表达式、方法引用、构造引用创建
Lambda普通用法相信大家都很熟悉了,这里主要说明Lambda的特殊情况:
方法引用和构造引用分为4类:
1. class_name::static_mathod_name
类中声明的静态方法可以这样引用
2. reference_name(object reference)::instance_method_name
实力方法引用
3. class_name::instance_method_name
// 第一个参数是调用此函数的引用
// 关键在于 是谁去调用注意顺序
建议查看GitHub上的例子以便理解
4. class_name::new
新生成一个对象
关于异常处理
我们自定义函数式接口呗,无非在JDK8的接口的基础上加上 throw Exception 不就好了么,但是事情往往不那么容易。首先,JDK8的Stream,Optional等等都是要基于JDK8本身的接口,我们自己写了一套那么我们自己用的爽,他本身的库函数就不爽了,再加上网络上其他的框架,不就没法兼容了么。
所以我们只能封装聚合一层,把JDK8的函数式接口来一个“倒卖二手货操作”:
实例代码中已经把43个函数式接口全部提供了一份unchecked的版本了哦,欢迎大家来star!