茴子的写法:关于JAVA中的函数传递语法糖:lambda

解决的问题:Java中实现函数传递。

在Java编程的实践过程中,有一些场景,我们希望能够将函数传递进去,不同的函数实现代表着不同的策略,这在JDK8以前,需要定义一个接口,这个接口中定义这个函数方法,然后传递这个接口的不同实现类进去,从而实现不同的策略,在JDK8及以后,可以使用lambda表达式做简化。

JDK8以前,我们想实现一个从不同数据库获取数据的策略,如下所示:

/**
 * 定义接口,从某一数据源获取数据
 */
public interface GetStrategy(){
    
    Bean get();

}
/**
 * 实现类:Mysql。
 */
public class MysqlGetStrategy(){
    private MysqlDao mysqlDao;
    Bean get(){
        return mysqlDao.get();
    }
} 
/**
 * 实现类:Squirrel
 */
public class RedisGetStrategy(){
    private RedisDao redisDao;
    Bean get(){
        return redisDao.get();
    }
}

/**
 * 获取数据后再处理数据
 */
public class Processor(){
    private GetStrategy getStrategy;
    public Processor(GetStrategy getStrategy){
        this.getStrategy = getStrategy;
    }
    private void process(){
        Bean bean = getStrategy.get();
        // process bean
    }
}

public static void main(String[] args){
    GetStrategy getStrategy = new MysqlGetStrategy();
    Processor processor = new Processor(getStrategy);
    processor.process();
}

可以看到上述代码比较繁复,为了实现将函数(即get方法)传递给Processor,我们需要

  1. 定义一个接口GetStrategy
  2. 定义一个实现类

当然,我们可以使用匿名内部类的方式简化上述的代码,如下所示:

/**
 * 定义接口,从某一数据源获取数据
 */
public interface GetStrategy(){
    
    Bean get();

}

/**
 * 获取数据后再处理数据
 */
public class Processor(){
    private GetStrategy getStrategy;
    public Processor(GetStrategy getStrategy){
        this.getStrategy = getStrategy;
    }
    private void process(){
        Bean bean = getStrategy.get();
        // process bean
    }
}

public static void main(String[] args){
    
    Processor processor = new Processor(new GetStrategy(){
        public Bean get(){
            return MysqlDao.get();
        }
    });
    processor.process();
}

但以上代码仍然比较复杂,复杂在

  1. 仍然需要定义一个接口GetStrategy。
  2. 在创建匿名内部类时,仍然较复杂,需要把方法名重新写一遍。

在JDK1.8后,提供了Lambda表达式,解决了函数传递的问题,不需要我们再做繁复的接口定义和实现类定义了,如下

private class Processor{
    /**
     * JDK 自定义函数,无参,有返回值。
     */
    private Supplier<Bean> supplier;
    
    public Processor(Supplier<Bean> supplier){
        this.supplier = supplier;
    }
    
    public void process(){
        Bean bean = supply.supply();
        // process逻辑
    }
}

public static void main(String[] args){
    Supply<Bean> supply = MysqlDao::get;
    Processor processor = new Processor(supply);
    processor.process();
}

从上述代码可以看到,此语法糖实现方式为

  1. 在java.util.function包中,定义了很多不同类别的接口(例如上述的Supplier接口),用以替代自己定义的接口(例如第一,二个代码片段中的GetStrategy)
  2. 使用lamdba表达式对匿名类的创建进行简化(例如MysqlDao::get)。

替代代码如下:

/**
 * JDK8以下:定义接口,从某一数据源获取数据
 */
public interface GetStrategy(){
    Bean get();
}

/**
 * JDK8替代:JDK自带Supplier接口
 */
Supplier<Bean> supplier

/**
 * JDK8以下匿名内部类
 */
new GetStrategy(){
        public Bean get(){
            return MysqlDao.get();
        }
    });

/**
 * JDK8替代:lambda匿名内部类表达方式
 */
MysqlDao::get

关于JDK8中java.util.function中的自定义接口

在java.util.function包中,有很多不同的接口用于替代一些简单的接口的定义,有Consumer,Supplier,Function,BiFunction等,分别对应了不同的函数实现,能够囊括大多数的函数的定义。

Consumer定义了一个参数,无返回值的函数。

BiConsumer定义了两个参数,无返回值的函数。

Supplier定义了一个有返回值,无参数的函数。

Function定义了一个有返回值,有参数的函数。

BiFunction定义了一个有返回值,有两个参数的函数。

但在一些函数参数超多的场景,例如三个参数,四个参数,java.util.function包中并未有相应的实现,如果我们需要的是有返回值的参数,这时可以引入如下jar包。

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.9.0</version>
</dependency>

其可以支持参数最多到8个的Function接口,至于8个以上,可能我们要重新审视一下此函数是否可以被重构以降低参数数量。

关于FunctionalInterface注解

作用:

使用这个FunctionalInterface注解标注的接口,会在编译期检查被标记的类型是否符合函数接口定义。

注:

如果从检查报错的目的上来看,FunctionalInterface这个注解比较鸡肋,因为我们写接口是需要用的,如果这个函数接口已经用lambda表达式的方式使用了,那他就是符合规范的,如果它不符合规范,我们用的时候就报错,我们在用的时候就能够达到编译期检查错误的目的。

如果从标注这是个函数接口来看,即示意,是有用的。

以下是翻译的FunctionalInterface的注解。

An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification.

一个用于定义一个接口是一个java语言规范定义的函数接口的注解。

Conceptually, a functional interface has exactly one abstract method. Since default methods have an implementation,‘they are not abstract.

概念上,一个函数接口有且只有一个抽象方法,由于default methods已经有实现,所以不是抽象的,

If an interface declares an abstract method overriding one of the public methods of java.lang.Object,

如果一个接口声明的抽象方法覆盖了Object中的一个public方法,

that also does not count toward the interface's abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.

那这样也不符合规定,因为这个接口的抽象方法继承了父类的实现。

Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

注意:函数接口的实例能够使用lambda表达式,方法引用,或者构造器引用创建。

If a type is annotated with this annotation type, compilers are required to generate an error message unless:

如果一个类型使用了这个注解,编译器会检查以下几项,不符合会报错。

The type is an interface type and not an annotation type, enum, or class.

这个类型是一个接口类型,不是注解,枚举,或者类。

The annotated type satisfies the requirements of a functional interface.

被注解的类型是一个函数接口。

However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a FunctionalInterface annotation is present on the interface declaration.

不过,不管使用不使用这个注解,编译器都会将符合函数接口定义的接口作为函数接口。

猜你喜欢

转载自blog.csdn.net/whodarewin2005/article/details/129886744