Lambda表达式之方法引用

一、前言

在日常开发过程中我们都是使用一些成熟的解决方案,例如在解决接口中的抽象方法该如何调用时,我们以前普遍采用的是俩种方式:

1、使用一个类去实现接口,在实现类中重写接口的方法,再创建实现类对象调用重写方法,实现方法调用

具体的代码实现如下:

1)接口定义

1
2
3
public interface IUsb {
    void cha(); // 默认被public abstract修饰
}

2)实现类

1
2
3
4
5
6
7
public class UsbImpl implements IUsb {
    @Override
    public void cha() {
        // TODO Auto-generated method stub
        System.out.println("实现类");
    }
}

3)测试类

1
2
3
4
5
6
public class Test {
    public static void main(String[] args) {
        UsbImpl u = new UsbImpl();
        u.cha();
    }
}

2、使用匿名内部类的方式,节省实现类的创建,让JVM帮助我们自动创建实现类对象

1)匿名内部类

1
2
3
4
5
6
7
IUsb u1 = new IUsb() {
    @Override
    public void cha() {
        System.out.println(123);
    }
};
u1.cha();

2)特别注意

1
2
3
4
5
6
7
8
new IUsb() {
    @Override
    public void cha() {
        System.out.println(123);
    }
};
以上代码不是在创建接口的对象,这里只是匿名内部类的语法格式。
接口不存在构造方法,不能创建对象

但是在实际开发中,会发现匿名内部类有点太臃肿,冗余代码较多,代码的可读性不强,所以在jdk1.8的版本中提出了使用Lambda表达式来简化匿名内部类。

二、Lambda方法引用

1、静态方法引用

1)案例1:将用户传入的字符串转换为对应Integer类型

a)函数式接口
1
2
3
4
public interface MyInterface {
    // 将字符串转换为Integer
    public Integer changeInt(String num);
}
b)解决方式
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Test {
    public static void main(String[] args) {
        // 使用匿名内部类写出来
        MyInterface mi = new MyInterface() {
            @Override
            public Integer changeInt(String num) {                          
                return Integer.parseInt(num);
            }
        };   
         
        //lambda的简化写法
        MyInterface mi = num -> Integer.parseInt(num);
 
        ut.println(mi.changeInt("123"));
        /** 以上代码就是使用lambda表达式简化后的写法,对比匿名内部类的方式可以看出,节省了一些冗余代码,只保留最核心的操作。但是在这段代码中我们还可以进一步进行优化操作。就是使用静态方法引用的方式,但它必须满足以下条件才可以。
         * 满足条件:
         * 1、被引用的静态方法参数列表和函数式接口中抽象方法的参数类型一致!!
         * 引用的静态方法:static int parseInt(String s)
         * 接口的抽象方法:Integer changeInt(String num);
         * 2、接口的抽象方法没有返回值,引用的静态方法可以有返回值也可以没有
         * 3、接口的抽象方法有返回值,引用的静态方法必须有相同类型的返回值!!
         * 上面的3个条件只需要同时满足1、2或者1、3就可以
         * 现在就是同时满足条件1和条件3。所以我们可以采用以下语法格式来简化:类名::静态方法名
         */
        MyInterface mi = Integer::parseInt;
        System.out.println(mi.changeInt("123"));
        /*
         * :: 这个格式通过代码跟踪知道调用的就是接口中的抽象方法changeInt
         * parseInt 这个方法就是调用Integer类中的静态方法
         */    }
}

2)案例2:比较俩个数的大小

看完上面的案列之后,再来加深一次印象,再实现一个需求来理解静态方法引用

a)函数式接口
1
2
3
4
public interface MyInterface {
       // 比较俩个数的大小
       public Integer getMax(int a,int b);
}
b)测试类
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public class Test {
    public static void main(String[] args) {
        // 调用比较大小的方法,使用匿名内部类实现
        MyInterface mi = new MyInterface() {
            @Override
            public Integer getMax(int a, int b) {
                return Integer.max(a, b);
            }
        };
        System.out.println(mi.getMax(10, 20));
              
        // lambda表达式的简化
        MyInterface mi = (a,b)->Integer.max(a, b);
        System.out.println(mi.getMax(10, 20));
              
        // 静态方法引用
        MyInterface mi = Integer::max;
        System.out.println(mi.getMax(10, 20));
    }
}

2、构造方法引用

需求:在接口中创建一个抽象方法 用于获取创建的用户对象

1)用户类

01
02
03
04
05
06
07
08
09
10
public class User {
    String name; //为了简化操作 没有按照标准封装的语法
    public User(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User [name=" + name + "]";
    }
}

2)接口的抽象方法

1
2
3
4
public interface MyInterface {
    // 创建一个方法  用于获取对象
    public User getUser(String name);
}

3)测试类

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestUser {
    public static void main(String[] args) {
        // 使用匿名内部类写出来
        MyInterface mi = new MyInterface() {
            @Override
            public User getUser(String name) {
                return new User(name);
            }
        };
        System.out.println(mi.getUser("gg"));
        MyInterface mi = name-> new User(name);
        System.out.println(mi.getUser("gg"));
 
        /**
        * 构造方法引用
        * 语法 :类名::new
        * 满足条件:构造方法与函数式接口的抽象方法参数列表一致
        * 接口方法:public User getUser(String name);
        * 构造方法:public User(String name) {}
        */
        MyInterface mi = User::new;
        System.out.println(mi.getUser("gg"));
    }
}

3、实例方法引用

1)案例1:判断指定的字符串对象是否以指定的后缀结束

这里使用的是java8自带的函数式接口Function

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class TestFunction {
    public static void main(String[] args) {
        /*
         * 使用function接口传入一个字符串 判断是否以指定的后缀结束
         * 泛型的类型必须是引用数据类型
         */        String strs = "xxx和xxxx的故事.mp4";
        Functionfun = new Function() {
            @Override
            public Boolean apply(String str) {
                //str 指定的后缀
                return strs.endsWith(str);
            }
        };
        System.out.println(fun.apply(".avi"));
        //lambda表达式的简化方式
        String strs = "xxx和xxxx的故事.mp4";
        Functionfun = str -> strs.endsWith(str);
        System.out.println(fun.apply(".avi"));
        /**
         * 语法 :对象名::非静态方法名
         * 满足条件:
         * 1、被引用的实例方法参数列表和函数式接口中抽象方法的参数一致!!
         *  实例方法:boolean endsWith(String suffix)
         *  抽象方法:Boolean apply(String str)
         * 2、接口的抽象方法没有返回值,引用的实例方法可以有返回值也可以没有
         * 3、接口的抽象方法有返回值,引用的实例方法必须有相同类型的返回值!!
         */
        String strs = "xxx和xxxx的故事.mp4";
        Functionfun = strs::endsWith;
        System.out.println(fun.apply(".avi"));
    }
}

2)案例2:获取指定字符串的长度

a)函数式接口
1
2
3
4
public interface MyInterface {
    // 返回指定字符串的长度
    public Integer getLength();
}
b)测试类
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class TestFunction {
    public static void main(String[] args) {            
        String strs = "xxx和xxxx的故事.mp4";
        MyInterface mi = new MyInterface() {
            @Override
            public Integer getLength() {
                // TODO Auto-generated method stub
                return strs.length();
            }
        };
        // lambda简化
        MyInterface mi = ()-> strs.length();            
        // 实例方法引用  对象名::实例方法名
        MyInterface mi = strs::length;
        System.out.println(mi.getLength());
    }
}

4、特定类型的方法引用

案例:传入字符串,获取到传入字符串的长度

1)函数式接口

1
2
3
4
public interface MyInterface {
    // 获取到字符串参数的长度     
    public Integer getLength(String str);
}

2)测试类

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
MyInterface mi = new MyInterface() {
    @Override
    public Integer getLength(String str) {
        return str.length();
    }
};
System.out.println(mi.getLength("123"));
// lambda简化
MyInterface mi = str->str.length();
System.out.println(mi.getLength("123"));
/**
 * 特定类型的方法引用
 *  语法:类名::非静态(实例)方法
 *  满足条件: 在抽象方法中,参数作为实例方法调用者,就可以简化
 * 在lambda简化的代码中我们正好是将参数作为length方法的调用者,
 * 在满足这个条件的前提下,就可以使用类名::实例方法的语法来完成特定类型引用。
 */MyInterface mi = String::length;
System.out.println(mi.getLength("123"));

三、总结

以上内容就是简单的总结了一下比较常用的方法引用方式,在初学者第一次接触lambda的方法引用时,不能急躁,需要多通过具体的案例来强化代码认识。并且这个方法引用一般还是搭配java8新特性里面的stream流来一起使用。

发表评论

欢迎阅读『Lambda表达式之方法引用|Java、JDK、开源组件、数据结构、算法|Nick Tan-梓潼Blog』