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:想應用實例或應用場景太費腦細胞~~!