栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

函数式编程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

函数式编程

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

比如 线程类,入参是一个 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 static 
    Collector> toMap(Function keyMapper,
                                    Function valueMapper,
                                    BinaryOperator mergeFunction) {
        return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
    }


 public static >
    Collector toMap(Function keyMapper,
                                Function 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,因此可以推断出 这个 实现 是 BinaryOperator,的实现,再根据泛型类型M,也能推断出 m1 m2的类型。

总结:

1. 首先 需要定义 一个 功能接口

2. 在定义一个方法时,如果使用功能接口,则功能接口 可以出现在 方法 返回类型 或者 方法 入参的位置

3. 在调用了 以 功能接口 为入参的 方法时,需要直接 在 方法的入参位置 实现 功能接口的 具体逻辑

4. 定义的方法 返回一个 功能接口时,则在 方法内部 要实现 该功能接口的 具体实现

5. 在阅读源码时,可以通过 方法的 入参是哪种功能接口 和 方法的返回是哪种功能接口 ,及 功能接口指定的类型  来判断 具体的实现逻辑功能

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

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号