java8知识总结_2.方法引用

2.方法引用

在正式讲解「方法引用」技术点前,我们先回顾下lambda表达式的基本用法。

首先lambda表达式的基本用途是用来实现函数式接口的方法。

这边文件中要用到以下两个java文件,我们这里先把这两个java文件建好。

文件1:Studen类,该类包含name和age两个属性,一个无参构造函数,一个有参构造函数(初始化name和age);

             两个属性的get/set方法,一个静态方法,一个实例方法,这两个方法都是用来比较学生的年龄。

具体请看下面的代码:

 1 public class Student {
 2 
 3     // 姓名
 4     private String name;
 5     // 年龄
 6     private int age;
 7 
 8     /**
 9      * 无参构造器
10      */
11     public Student() {
12 
13     }
14 
15     /**
16      * 有参构造器_初始化学生属性
17      * 
18      * @param name
19      *            姓名
20      * @param age
21      *            年龄
22      */
23     public Student(String name, int age) {
24         this.name = name;
25         this.age = age;
26     }
27 
28     public String getName() {
29         return name;
30     }
31 
32     // ###### get/set方法 START ######
33     public void setName(String name) {
34         this.name = name;
35     }
36 
37     public int getAge() {
38         return age;
39     }
40 
41     public void setAge(int age) {
42         this.age = age;
43     }
44     // ###### get/set方法 END ######
45 
46     /**
47      * 静态方法_比较两个学生的年龄
48      * 
49      * @param std1
50      *            学生实例1
51      * @param std2
52      *            学生实例2
53      * @return 等于0   年龄相等
54      *         大于0   学生实例1的年龄 > 学生实例2的年龄
55      *         小于0   学生实例1的年龄 < 学生实例2的年龄
56      */
57     public static int compareAgeStatic(Student std1, Student std2) {
58         return std1.getAge() - std2.getAge();
59     }
60     
61     /**
62      * 普通方法_比较两个学生的年龄
63      * 
64      * @param std1
65      *            学生实例1
66      * @param std2
67      *            学生实例2
68      * @return 等于0   年龄相等
69      *         大于0   学生实例1的年龄 > 学生实例2的年龄
70      *         小于0   学生实例1的年龄 < 学生实例2的年龄
71      */
72     public int compareAge(Student std1, Student std2) {
73         return std1.getAge() - std2.getAge();
74     }
75 }

文件2:CompareStudentAge接口,该接口为函数式接口,包含一个抽象方法。

具体请看下面的代码:

1 @FunctionalInterface
2 public interface CompareStudentAge {
3 
4     /** 比较两个学生的年龄 */
5     int compareAge(Student std1,Student std2);
6 }

下面我们用之前学习过的lambda表达式,来实现上面接口的compareAge方法。

 1         // lambda表达式实现接口的方法体
 2         CompareStudentAge csa = (Student s1, Student s2) -> {
 3             return s1.getAge() - s2.getAge();
 4         };
 5         
 6         // 声明两个学生实例
 7         Student std1 = new Student("yubx", 36);
 8         Student std2 = new Student("ldm", 35);
 9 
10         // 调用接口的compareAge方法
11         int result = csa.compareAge(std1, std2);
12         
13         // 打印执行结构
14         System.out.println(result);

上面是普通的lambda实现方式,执行结果:1

基于上面的两个java文件以及对lambda表达式基本写法的回顾,我们来学习java8的另一个特性:「方法引用」

2-1.概述

方法引用可以直接引用已有Java类或对象(实例)的方法或构造器。

方法引用的一般用途是与lambda联合使用。

方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

2-2.语法

类名/对象::静态方法名/实例方法名

2-3.分类

  1. 类名::静态方法名
  2. 对象::实例方法名
  3. 类名::实例方法名
  4. 类名::new(构造器)

2-4.实例

1.类名::静态方法名

我们用Student类中的静态方法compareAgeStatic来替换lambda的实现。

 1         // 「类名::静态方法名」方法引用
 2         CompareStudentAge csa = Student::compareAgeStatic;
 3         
 4         // 声明两个学生实例
 5         Student std1 = new Student("yubx", 36);
 6         Student std2 = new Student("ldm", 35);
 7 
 8         // 调用接口的compareAge方法
 9         int result = csa.compareAge(std1, std2);
10         
11         // 打印执行结构
12         System.out.println(result);

执行结果:1

2.对象::实例方法名

我们用Student类中的实例方法compareAge来替换lambda的实现。

 

 1         // 构造学生类
 2         Student stdInstance = new Student();
 3         
 4         // 「对象::实例方法名」
 5         CompareStudentAge csa = stdInstance::compareAge;
 6         
 7         // 声明两个学生实例
 8         Student std1 = new Student("yubx", 36);
 9         Student std2 = new Student("ldm", 35);
10 
11         // 调用接口的compareAge方法
12         int result = csa.compareAge(std1, std2);
13         
14         // 打印执行结构
15         System.out.println(result);

执行结果:1

3.类名::实例方法名

这种类型,对有些小伙伴来说,可能理解起来相对比较难,我尽量说得仔细鞋。

这里我们要用到this关键字。

我们首先来看下this关键字的一种用法。

下面利用JDK的String类中的equals的代码来进行说明。

 1     /**
 2      * Compares this string to the specified object.  The result is {@code
 3      * true} if and only if the argument is not {@code null} and is a {@code
 4      * String} object that represents the same sequence of characters as this
 5      * object.
 6      *
 7      * @param  anObject
 8      *         The object to compare this {@code String} against
 9      *
10      * @return  {@code true} if the given object represents a {@code String}
11      *          equivalent to this string, {@code false} otherwise
12      *
13      * @see  #compareTo(String)
14      * @see  #equalsIgnoreCase(String)
15      */
16     public boolean equals(Object anObject) {
17         if (this == anObject) {
18             return true;
19         }
20         if (anObject instanceof String) {
21             String anotherString = (String)anObject;
22             int n = value.length;
23             if (n == anotherString.value.length) {
24                 char v1[] = value;
25                 char v2[] = anotherString.value;
26                 int i = 0;
27                 while (n-- != 0) {
28                     if (v1[i] != v2[i])
29                         return false;
30                     i++;
31                 }
32                 return true;
33             }
34         }
35         return false;
36     }

问题:先看第17行的this关键字,这个this代表的是哪个实例?

想想我们平时调用equals方法的写法:

1         String st1 = "abc";
2         String st2 = "bcd";
3         boolean ret = st1.equals(st2);

猜想:通过上面的代码,我们大致可以猜到:JDK中的equals方法中的this指代的可能是上记代码中的str1。

结论:java中对于实例方法,「this引用」隐式的作为第一个参数传递进去,并且默认用这个this调用该实例方法。

(这种调用方式和python的方法调用很像,有兴趣的小伙伴可以去查阅下相关资料。)

方法引用就利用了上面的原理,可以实现「类名::实例方法名」的方式。

综上,我们在上面的Studen类中追加如下实例方法:

    /**
     * 普通方法_比较两个学生的年龄
     * 
     * @param std
     *            学生实例
     * @return 等于0   年龄相等
     *         大于0   学生实例1的年龄 > 学生实例2的年龄
     *         小于0   学生实例1的年龄 < 学生实例2的年龄
     */
    public int compareAge(Student std) {
        return this.getAge() - std.getAge();
    }

上面的this关键字指代的就是方法接口中的第一个参数。

这时,我们用「类名::实例方法名」的方式,重写lambda的实现。

 1         // 「类名::实例方法名」
 2         CompareStudentAge csa = Student::compareAge;
 3         
 4         // 声明两个学生实例
 5         Student std1 = new Student("yubx", 36);
 6         Student std2 = new Student("ldm", 35);
 7         
 8         // 调用接口的compareAge方法
 9         int result = csa.compareAge(std1, std2);
10         
11         // 打印执行结构
12         System.out.println(result);

上面的代码,实际上用的就是std1去调用只有一个参数的实例方法compareAge。

执行结果:1

注:如果小伙伴们对上面的代码处理逻辑,理解上还是不够清晰的话,

建议自己多动手去写些传统代码,加深对this隐式传递处理方式的理解。

4.类名::new(构造器)

首先我们假想一个情景:

・有一个函数式接口,该接口的抽象方法接受学生姓名和学生你年龄做参数。

(和Student类中有参构造函数的参数列表一致)

・在Student类中追加一个实例方法,该方法接受一个默认this实例引用参数和一个int类型参数(要增加的年龄)

    

1 @FunctionalInterface
2 public interface ModifyStudent {
3 
4     /** 修改学生的年龄 */
5     Student modify(String str,int i);
6 }
 1     /**
 2      * 修改yubx的年龄
 3      * 
 4      * @param addAge
 5      *            增加的年龄
 6      * @return 该学生的实例
 7      */
 8     public Student modify(int addAge) {
 9         if (!this.name.equals("yubx")) {
10             System.out.println("yubx以外的学生不能修改Ta的年龄!");
11             return this;
12         }
13         this.setAge(this.age + addAge);
14         return this;
15     }

基于上面的代码,我们用「类名::new(构造器)」的方式重写lambda的实现。

 1         // 「类名::new(构造函数)」
 2         ModifyStudent student = Student::new;
 3         
 4         // 调用接Student类中的compareAge方法
 5         Student newStd = student.modify("yubx", 36);
 6         // 调用Student类中的modify方法
 7         newStd.modify(10);
 8         
 9         // 打印执行结构
10         System.out.println("学生"+newStd.getName()+"的年龄是" +newStd.getAge());

执行结果:学生yubx的年龄是46

我们把上面第5行的参数列表修改下:

 1         // 「类名::new(构造函数)」
 2         ModifyStudent student = Student::new;
 3         
 4         // 调用接Student类中的compareAge方法
 5 //        Student newStd = student.modify("yubx", 36);
 6         Student newStd = student.modify("ldm", 35);
 7         // 调用Student类中的modify方法
 8         newStd.modify(10);
 9         
10         // 打印执行结构
11         System.out.println("学生"+newStd.getName()+"的年龄是" +newStd.getAge());

执行结果:

yubx以外的学生不能修改Ta的年龄!

学生ldm的年龄是35

2-5.综上小结

上面我们用具体实例结合lambda表达式,学习了方法引用的四种方式,从中我们总结一个结论:

使用「方法引用」时,要引用的方法(也就是操作符「::」后的方法)的参数列表,

必须要与lambda表达式要实现的函数式接口的方法参数列表一致。

说到底,lambda表达式的住哟用途:实现函数式接口的抽象方法。

而「方法引用」就是用来替换lambda表达式的"方法体"(具体实现)。

以上,希望还没理解吃透的小伙伴,查找以下其他资料,多多练习。如果有心得,欢迎底下留言交流!

PS:想应用实例或应用场景太费脑细胞~~!

 

posted @ 2020-03-21 21:32  yubx  阅读(245)  评论(7编辑  收藏  举报