函数式编程

15 篇文章 0 订阅

在日常的开发过程中,当我们调用某个方法时,方法的入参 是一个接口,那么可以通过 匿名内部类的方式来实现。

比如 线程类,入参是一个 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的类型。

总结:

  1. 出现在方法参数的位置: 在调用了 以 功能接口 为入参的 方法时,需要直接 在 方法的入参位置 实现 功能接口的 具体逻辑。但是需要注意到是,功能接口的方法的参数问题,有时候可能不知道 参数是哪里来的?是什么类型?需要结合 功能接口的方法的调用来看
  2. 出现在方法返回类型的位置:定义的方法 返回一个 功能接口时,则在 方法内部 要实现 该功能接口的 具体实现。下面的例子,当看到代码的时候,如果对函数式编程不熟悉的,是看不懂的,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);
}

在阅读源码时,当代码很奇怪时,就要想到这里可能用的是函数式接口来实现的。可以通过 方法的 入参是哪种功能接口 和 方法的返回是哪种功能接口 ,及 功能接口指定的类型  来判断 具体的实现逻辑功能

现在再看一遍上面的示例,是不是简单很多。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值