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.分类
- 类名::静态方法名
- 对象::实例方法名
- 类名::实例方法名
- 类名::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:想应用实例或应用场景太费脑细胞~~!