在日常的开发过程中,当我们调用某个方法时,方法的入参 是一个接口,那么可以通过 匿名内部类的方式来实现。
比如 线程类,入参是一个 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增加了一个注解:
/* @jls 4.3.2. The Class Object
* @jls 9.8 Functional Interfaces
* @jls 9.4.3 Interface Method Body
* @since 1.8
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
由Java语言规范定义的 声明式 注解,用来表示 接口 定义 被用来完成 某种特定功能。
Java 提供了几种内置的功能接口:
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
带来的问题:
由于 去除了 类型 和 方法名, 直接编写的 具体逻辑,所以 有时候看源码时 会比较晕。
比如:
public static <T, C extends Collection<T>>
Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
return new CollectorImpl<>(collectionFactory, Collection<T>::add,
(r1, r2) -> { r1.addAll(r2); return r1; },
CH_ID);
}
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Set<Characteristics> characteristics) {
this(supplier, accumulator, combiner, castingIdentity(), characteristics);
}
@FunctionalInterface
public interface BiConsumer<T, U> {
/**
* Performs this operation on the given arguments.
*
* @param t the first input argument
* @param u the second input argument
*/
void accept(T t, U u);
}
@FunctionalInterface
public interface BiFunction<T, U, R> {
/**
* Applies this function to the given arguments.
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);
}
这个 toCollection方法 怎么回事?和平时看的代码完全不一样,这是什么意思?
分析一下:
1. 首先 是一个 泛型 静态方法
2. 入参是一个 函数式接口
3. CollectorImpl类的构造方法有 三个 函数式 接口,在构造方法实例化 对象时,直接在 入参的位置 使用了 函数式编程的语法
第一个参数 Supplier<A> supplier:
分析:Supplier 的方法是 返回 A,而A 是 Collection<T> 类型的集合
第二个参数 BiConsumer<A,T> accumulator:
分析: BiConsumer 是需要接收两个参数。 通过分析,第一个参数为A,是Collection<T>集合,第二个参数是T,也就是集合中元素的类型。BiConsumer 的实现为 调用集合的 add 方法。 也就是A.add(T)。在阅读代码的时候,可以把 具体接口类型 和方法名 丢掉,不用关注,因为 最终 程序会执行的 就是 这个add方法,表示 将 T 添加到集合中
第三个参数BinaryOperator<A> combiner,把 具体接口类型 和方法名丢掉,不关注。那就可以看出 第三个参数的对象 要实现的功能就是 调用 addAll方法,并返回r1
在看一个例子:
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}
private static <K, V, M extends Map<K,V>>
BinaryOperator<M> mapMerger(BinaryOperator<V> mergeFunction) {
return (m1, m2) -> {
for (Map.Entry<K,V> 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 <K, V, M extends Map<K,V>>
BinaryOperator<M> mapMerger(BinaryOperator<V> mergeFunction) {
BinaryOperator ma = new BinaryOperator<M>(){
@Override
public M apply(M m1, M m2) {
for (Map.Entry<K,V> e : m2.entrySet())
m1.merge(e.getKey(), e.getValue(), mergeFunction);
return m1;
}
};
return ma;
}
所以 m1 和 m2 都是Map类型。
另外一点是,mapMerger 方法体是直接return 了一个 函数式接口的 具体实现,方法签名的返回类型又是 BinaryOperator<M>,因此可以推断出 这个 实现 是 BinaryOperator<M>,的实现,再根据泛型类型M,也能推断出 m1 m2的类型。
总结:
- 出现在方法参数的位置: 在调用了 以 功能接口 为入参的 方法时,需要直接 在 方法的入参位置 实现 功能接口的 具体逻辑。但是需要注意到是,功能接口的方法的参数问题,有时候可能不知道 参数是哪里来的?是什么类型?需要结合 功能接口的方法的调用来看
- 出现在方法返回类型的位置:定义的方法 返回一个 功能接口时,则在 方法内部 要实现 该功能接口的 具体实现。下面的例子,当看到代码的时候,如果对函数式编程不熟悉的,是看不懂的,getSelfInitializer 方法有返回类型的,为什么selfInitialize返回的是空?selfInitialize 方法明明有参数的,为什么调用的时候没有传?参数什么时候传?等等。实际这是一个函数式接口,这里只是定义了接口的实现逻辑,并不是调用执行的地方。这个例子也可以说明:首先是定义函数式接口的实现逻辑,这里并不是执行了。 然后再某个时机通过函数式接口的方法名来调用。所以,定义和调用,乍一看好像没什么关系,时机调用执行的是定义的逻辑。
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
@FunctionalInterface
public interface ServletContextInitializer {
/**
* Configure the given {@link ServletContext} with any servlets, filters, listeners
* context-params and attributes necessary for initialization.
* @param servletContext the {@code ServletContext} to initialize
* @throws ServletException if any call against the given {@code ServletContext}
* throws a {@code ServletException}
*/
void onStartup(ServletContext servletContext) throws ServletException;
}
// 调用的地方:getWebServer入参是一个函数式接口
this.webServer = factory.getWebServer(getSelfInitializer());
// 真正调用的地方:ServletContextInitializer 是一个函数式接口
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
在阅读源码时,当代码很奇怪时,就要想到这里可能用的是函数式接口来实现的。可以通过 方法的 入参是哪种功能接口 和 方法的返回是哪种功能接口 ,及 功能接口指定的类型 来判断 具体的实现逻辑功能
现在再看一遍上面的示例,是不是简单很多。