在日常的开发过程中,当我们调用某个方法时,方法的入参 是一个接口,那么可以通过 匿名内部类的方式来实现。
比如 线程类,入参是一个 Runnable 接口,入参只能是一个实现了Runnable接口的 类对象:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
一般的写法:
new Thread(new Runnable() {
@Override
public void run() {
}
});
这种写法,看起来没什么问题,但是,如果 代码中有类似大量的代码,就会觉得不是很好看了。
于是,使用函数式编程写法:
new Thread(()->{});
这样看起来,是不是特别简洁。
针对第一种写法和第二种写法,实际上都是 JDK的一种语法,作为使用者,只能按照别人的规则来使用就好了。他们表达的意思都是一样的,就是 入参 是Runnable接口的实现类对象,该对象实现了 接口方法,方法的具体逻辑是这样的。只是 第二种写法 直接 表现出 方法的具体逻辑,而去除了具体类型和具体方法这些 没有用的信息。
语法:
JDK8增加了一个注解:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
由Java语言规范定义的 声明式 注解,用来表示 接口 定义 被用来完成 某种特定功能。
Java 提供了几种内置的功能接口:
@FunctionalInterface public interface Function{ R apply(T t); } @FunctionalInterface public interface Consumer { void accept(T t); } @FunctionalInterface public interface Supplier { T get(); } @FunctionalInterface public interface Predicate { boolean test(T t); }
带来的问题:
由于 去除了 类型 和 方法名, 直接编写的 具体逻辑,所以 有时候看源码时 会比较晕。
比如:
public static> Collector toCollection(Supplier collectionFactory) { return new CollectorImpl<>(collectionFactory, Collection ::add, (r1, r2) -> { r1.addAll(r2); return r1; }, CH_ID); } CollectorImpl(Supplier supplier, BiConsumer accumulator, BinaryOperator combiner, Set characteristics) { this(supplier, accumulator, combiner, castingIdentity(), characteristics); }
这个 toCollection方法 怎么回事?和平时看的代码完全不一样,这是什么意思?
分析一下:
1. 首先 是一个 泛型 静态方法
2. 入参是一个 函数式接口
3. CollectorImpl类的构造方法有 三个 函数式 接口,在构造方法实例化 对象时,直接在 入参的位置 使用了 函数式编程的语法
第一个参数 Supplier supplier 是 用的 方法入参
第二个参数 BiConsumer accumulator, 实现为 调用集合的 add 方法。 在阅读代码的时候,可以把 具体接口类型 和方法名 丢掉,不用关注,因为 最终 程序会执行的 就是 这个add方法,表示 将 T 添加到集合中
第三个参数BinaryOperator combiner,把 具体接口类型 和方法名丢掉,不关注。那就可以看出 第三个参数的对象 要实现的功能就是 调用 addAll方法,并返回r1
在看一个例子:
public staticCollector > toMap(Function super T, ? extends K> keyMapper, Function super T, ? extends U> valueMapper, BinaryOperator mergeFunction) { return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); } public static > Collector toMap(Function super T, ? extends K> keyMapper, Function super T, ? extends U> valueMapper, BinaryOperator mergeFunction, Supplier mapSupplier) { BiConsumer accumulator = (map, element) -> map.merge(keyMapper.apply(element), valueMapper.apply(element), mergeFunction); return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID); } private static > BinaryOperator mapMerger(BinaryOperator mergeFunction) { return (m1, m2) -> { for (Map.Entry e : m2.entrySet()) m1.merge(e.getKey(), e.getValue(), mergeFunction); return m1; }; }
上面的toMap方法有三个入参,都是 函数式接口,表名 再调用 一个方法时,需要编写出 具体的 逻辑,如:
Collectors.toMap(k->k,v->v,(k1,k2)->k1)
即:定义方法时,入参是 函数式接口,那么 再调用这个方法时,就需要 传入 具体的 实现逻辑,包括 方法入参 和 方法逻辑,不用关注 参数类型 、方法名这些。非常的简洁,要有这种思维就行。
下面的toMap,方法体内有一个 函数式编程的实现,没有写在方法参数上,可以看出 accumulator 变量是 CollectorImpl的第二个参数。怎么确定 入参 是什么类型呢?可以看一下 定义变量时是指定了 BiConsumer的泛型类型的。所以功能就了解了。
下面的mapMerger怎么看?
肯定的是 直接是一个函数式编程 的 方法实现,有两个入参,具体是哪种类型?
如果换一种写法,这样就很清楚这个功能了:
private static> BinaryOperator mapMerger(BinaryOperator mergeFunction) { BinaryOperator ma = new BinaryOperator (){ @Override public M apply(M m1, M m2) { for (Map.Entry e : m2.entrySet()) m1.merge(e.getKey(), e.getValue(), mergeFunction); return m1; } }; return ma; }
所以 m1 和 m2 都是Map类型。
另外一点是,mapMerger 方法体是直接return 了一个 函数式接口的 具体实现,方法签名的返回类型又是 BinaryOperator
总结:
1. 首先 需要定义 一个 功能接口
2. 在定义一个方法时,如果使用功能接口,则功能接口 可以出现在 方法 返回类型 或者 方法 入参的位置
3. 在调用了 以 功能接口 为入参的 方法时,需要直接 在 方法的入参位置 实现 功能接口的 具体逻辑
4. 定义的方法 返回一个 功能接口时,则在 方法内部 要实现 该功能接口的 具体实现
5. 在阅读源码时,可以通过 方法的 入参是哪种功能接口 和 方法的返回是哪种功能接口 ,及 功能接口指定的类型 来判断 具体的实现逻辑功能
现在再看一遍上面的示例,是不是简单很多。



