Administrator
发布于 2024-07-11 / 6 阅读
0
0

Two Colon Expression and Java Function Interface 2024

Two Colon Expression & Java Function Interface 2024

基础理论

这篇文章,主要讲述2个概念:函数式接口 和 方法的引用

函数式接口

下面,是java中定义好的,8个函数式接口

Unary 一元的

Binary 二元的

nametypedescription
ConsumerConsumer< T >接收T对象,不返回值
PredicatePredicate< T >接收T对象并返回boolean
FunctionFunction< T, R >接收T对象,返回R对象
SupplierSupplier< T >提供T对象(例如工厂),不接收值
UnaryOperatorUnaryOperator< T >接收T对象,返回T对象
BiConsumerBiConsumer<T, U>接收T对象和U对象,不返回值
BiPredicateBiPredicate<T, U>接收T对象和U对象,返回boolean
BiFunctionBiFunction<T, U, R>接收T对象和U对

方法的引用

Person p = new Student();
System.out.println(p.getName());  // 属性
p.eat(); // 行为

谈到引用,我们一般用于指,父类引用 指向 子类对象。

也即引用,其实就是对被引用对象的行为和特性的进一步抽象后,得到的结果。

通过这个引用,来表达被引用对象的一些特性和行为(对象有哪些方法和属性)


方法分为静态方法和实例方法,如果是实例方法,描述该方法,还需要指定具体是哪个实例对象的。

所以,想要描述一个需要的方法,至少需要以下几个方面:
1.方法名称
2.方法参数,包括参数类型、参数个数、参数顺序
3.该方法的返回值类型
4.如果该方法,是一个实例方法,那么还需要指明,具体是哪个实例对象的


那么对一个方法的引用,同样是指,对一个方法的行为和特性的进一步抽象后,得到的结果。
比如,该方法是否需要参数,要几个参数,是否有返回值,返回值的类型是什么,等等


而函数式接口,它正好满足了,对一个方法的行为和特性的进一步抽象后的结果。
比如常见的几个函数式接口,如下:
Function就是需要1个参数,有1个返回值;
Supplier,就是不需要参数,有1个返回值;
Consumer,就是需要一个参数,没有返回值;

所以,Java中,使用函数式接口,来作为方法的引用

我们要理清楚几个概念:

lambda表达式 和 双冒号表达式的作用:表达清楚一个需要的方法(方法名是什么、属于哪个类、属于哪个实例)

使用函数式接口的作用:作为方法的引用,表达 一个方法被抽象后,具有哪些特性和行为

下面,我们来结合具体的实例,来说明

实例说明

Class::staticMethod 和 Function


public class MonQueryRespDTO {

    public static MonQueryRespDTO bo2Dto(DeviceMonitorBO deviceMonitorBO)    {
        MonQueryRespDTO monQueryRespDTO = new MonQueryRespDTO();
        monQueryRespDTO.setId(deviceMonitorBO.getId());
        monQueryRespDTO.setName(deviceMonitorBO.getName());
        monQueryRespDTO.setBoxNo(deviceMonitorBO.getBoxNo());
        monQueryRespDTO.setBoxName(deviceMonitorBO.getBoxName());
        
        return monQueryRespDTO;
    }
        
}

可以看到MonQueryRespDTO有一个静态方法bo2Dto

那接下来,如果,我们想使用函数式接口,来做bo2Dto方法的引用,应该选择哪一个呢?

我们来看下,bo2Dto的方法,有哪些特性:
参数列表:需要1个参数
返回值:有返回值

所以,我们使用的函数式接口,是Function<DeviceMonitorBO, MonQueryRespDTO>

代码如下:

Function<DeviceMonitorBO, MonQueryRespDTO> function = MonQueryRespDTO::bo2Dto;

这里使用了双冒号表达式,描述了需要一个方法:静态方法bo2Dto,同时使用Function这个函数式接口,作为此方法的引用,表达该方法被抽象后,需要一个参数,同时有返回值


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);
}

Class::instanceMethod 和 Function/BiConsumer等

首先,实例方法,相比与静态方法,多了一个待描述的因子,需要指明是哪个对象的实例方法

因此,使用函数式接口,作为实例方法的引用时,函数式接口的第一个参数作为方法的调用者,剩余参数,作为实例方法的入参

上面的这句话,是什么意思呢?我们来看下实例:

public class MonQueryRespDTO {
    
    private String boxName;
    
    public String getBoxName() {
        return boxName;
    }

    public void setBoxName(String boxName) {
        this.boxName = boxName;
    }

}

我们来看下,getBoxName的方法,有哪些特性:
参数列表:不需要参数
返回值:有返回值

一般来说,我们可以考虑,采用Supplier函数式接口,来做此方法的引用即可。

但是,因为getBoxName是实例方法,因此需要指明是哪个对象的,所以必须要使用需要参数的函数式接口,并且第一个参数,必须是getBoxName方法的调用者。所以,必须使用的函数式接口是Function<MonQueryRespDTO, String>


代码如下:

Function<MonQueryRespDTO, String> function2 = MonQueryRespDTO::getBoxName;

那接下来,如果,我们想使用函数式接口,来做setBoxName方法的引用,应该选择哪一个呢?

我们来看下,setBoxName的方法,有哪些特性:
参数列表:需要1个参数
返回值:没有返回值

因为setBoxName是实例方法,因此需要指明是哪个对象的,所以必须要使用需要参数的函数式接口,并且第一个参数,必须是setBoxName方法的调用者。

所以,选择的函数式接口是BiConsumer<MonQueryRespDTO, String>


代码如下:

 BiConsumer<MonQueryRespDTO, String> setBoxName = MonQueryRespDTO::setBoxName;

object::instanceMethod 和 Predicate

我们都知道,List集合,有一个contains方法,判断某个元素,是否存在于集合中,如下:

java.util.List#contains

boolean contains(Object o);

我们来看下,contains的方法,有哪些特性:
参数列表:需要1个参数
返回值:有返回值,并且返回值是boolean类型的

相比与之前的class::instanceMethod,因为object::instanceMethod,已经指明了该实例方法,是哪个对象的,所以,在选择哪个函数式接口,作为此方法的引用时,不需要考虑函数式接口的第一个参数,作为contains方法的调用者。

综上,我们选择的函数式接口是Predicate<String>


代码如下:

public static void main(String[] args) {
        List<String> originList = new ArrayList<>();
        originList.add("aaaa");
        originList.add("bbbb");
        originList.add("cccc");

        Predicate<String> predicate = originList::contains;
        boolean containsA = predicate.test("aaaa");
        boolean containsD = predicate.test("dddd");

        System.out.println(containsA);
        System.out.println(containsD);
    }

Construct Method 和 Function Interface

上面,我们说的方法,都是正常的方法,那对于构造方法,应该怎么使用双冒号表达式,来表达需要的是哪个类的哪个构造方法?以及,如何选择函数式接口呢?

语法格式:ClassName::new

  调用哪个构造器取决于函数式接口中的方法形参的定义,Lambda会自动根据接口方法的定义推断你要调用的构造器,也就是说需要调用的构造器的参数列表要与函数式接口中的抽象方法的参数列表保持一致

public class Person {
    private String name;

    private Integer age;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Person类,有3个构造方法,我们来看下,分别选择什么样的函数式接口,作为3个构造方法的引用呢?

第一个构造方法:
参数列表:不需要参数
返回值:有返回值

所以,选择的函数式接口,是Supplier<Person>


第二个构造方法:
参数列表:需要1个String类型的参数
返回值:有返回值

所以,选择的函数式接口,是Function<String,Person>


第三个构造方法:
参数列表:需要2个参数,分别是String 和 Integer
返回值:有返回值

所以,选择的函数式接口,是BiFunction<String,Integer,Person>


接下来,我们使用双冒号表达式和函数式接口,来演示分别调用3个构造方法:


public static void main(String[] args) {

        Supplier<Person> supplier = Person::new;
        Function<String,Person> function = Person::new;
        BiFunction<String,Integer,Person> biFunction = Person::new;

        //
        Person p1 = supplier.get();
        System.out.println(JSON.toJSONString(p1));


        Person p2 = function.apply("zhangsan");
        System.out.println(JSON.toJSONString(p2));

        Person p3 = biFunction.apply("lisi", 20);
        System.out.println(JSON.toJSONString(p3));

    }

打印出的结果如下:

{}
{"name":"zhangsan"}
{"age":20,"name":"lisi"}

数组创建 和 函数式接口

正常情况下,创建数组,需要指明该数组的容量


public static void main(String[] args) {
        // 正常情况下,创建数组,需要指明该数组的容量
        String[] strArray = new String[3];
        strArray[0]="hello";
        strArray[1]=",";
        strArray[2]="world";
        System.out.println(JSON.toJSONString(strArray));// ["hello",",","world"]
    }

所以,数组的创建方法,
参数列表:需要1个参数,说明该数组的容量
返回值:有返回值

所以,作为此方法的引用,选择的函数式接口,是Function<Integer,String[]>


public static void main(String[] args) {
        Function<Integer,String[]> function = String[]::new;
        // 指明该数组的容量为3
        String[] strArray = function.apply(3);
        strArray[0]="hello";
        strArray[1]=",";
        strArray[2]="world";
        System.out.println(JSON.toJSONString(strArray));
    }

Comparator

Comparator,也是一种函数式接口,类似于BiFunction,如下:

@FunctionalInterface
public interface Comparator<T> {
    
     int compare(T o1, T o2);
  
}


接下来,我们看下java.lang.Integer#compare

// java.lang.Integer#compare
public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }

我们来看下,Integer的compare的方法,有哪些特性:
参数列表:需要2个参数
返回值:有返回值,并且返回值是int类型的


所以,Integer的compare方法的引用,选择的函数式接口,是Comparator<Integer>

代码如下:

public static void main(String[] args) {
        Comparator<Integer> compare = Integer::compare;
        int result = compare.compare(10, 20);
        System.out.println(result);  // -1
    }

双冒号表达式,更多拓展

refer to : https://www.chenx.tech/double-colon/


评论