方法引用
1.方法引用簡述
方法引用是用來直接訪問類或者實例的已經存在的方法或者構造方法。方法引用提供了一種引用而不執行方法的方式,它需要由兼容的函數式接口構成的目標類型上下文。計算時,方法引用會創建函數式接口的一個實例。
當Lambda表達式中只是執行一個方法調用時,不用Lambda表達式,直接通過方法引用的形式可讀性更高一些。方法引用是一種更簡潔易懂的Lambda表達式。
2.作用
方法引用的唯一用途是支持Lambda的簡寫。
方法引用提高了代碼的可讀性,也使邏輯更加清晰。(優點)
3.組成
使用::操作符將方法名和對象或類的名字分隔開。
“::” 是域操作符(也可以稱作定界符、分隔符)。
eg:

4.分類
1)靜態方法引用
組成語法格式:ClassName::staticMethodName
Note:
靜態方法引用比較容易理解,和靜態方法調用相比,只是把.換為::在目標類型兼容的任何地方,都可以使用靜態方法引用。
eg:
--String::valueOf 等價於lambda表達式(s) -> String.valueOf(s)
--Math::pow 等價於lambda表達式 (x, y) -> Math.pow(x, y);
--假設需要從一個數字列表中找出最大的一個數字。
方法引用方式:
(max是一Collections裡的一個靜態方法,它需要傳入一個List類型的參數。)
Function, Integer> maxFn =Collections::max;
maxFn.apply(Arrays.asList(1, 10, 3, 5))。
上面等價於Lambda表達式Function, Integer> maxFn = (numbers) -> Collections.max(numbers);。
-- 以字符串反轉為例:
/*
* 函數式接口
* */
interface StringFunc {
String func(String n);
}
class MyStringOps {
//靜態方法: 反轉字符串
public static String strReverse(String str) {
String result = "";
for (int i = str.length() - 1; i >= 0; i--) {
result += str.charAt(i);
}
return result;
}
}
class MethodRefDemo {
public static String stringOp(StringFunc sf, String s) {
return sf.func(s);
}
public static void main(String[] args) {
String inStr = "lambda add power to Java";
//MyStringOps::strReverse 相當於實現了接口方法func() ,並在接口方法func()中作了MyStringOps.strReverse()操作
String outStr = stringOp(MyStringOps::strReverse, inStr);
System.out.println("Original string: " + inStr);
System.out.println("String reserved: " + outStr);
}
}
輸出結果:
Original string: lambda add power to Java
String reserved: avaJ ot rewop dda adbmal
分析:
在程序中,特別注意下面這行代碼:String outStr = stringOp(MyStringOps::strReverse, inStr);
其中將對MyStringOps內聲明的靜態方法strReverse()的引用傳遞給stringOp()方法的第一個參數。可以這麼做,因為
strReverse與StringFunc函數式接口兼容。因此,表達式MyStringOps::strReverse的計算結果為對象引用,其中,
strReverse提供了StringFunc的func()方法的實現。
-- 找到列表中具有最大值的對象
(找到集合中最大元素的一種方法是使用Collections類定義的max()方法。對於這裡使用的max()版本,必須傳遞一個集合引用,以及一個實現了Comparator接口的對象的實例。Comparator接口指定如何比較兩個對象,它只定義了抽象方法compare(),該方法接受兩個參數,其類型均為要比較的對象的類型。如果第一個參數大於第二個參數,該方法返回一個正數;如果兩個參數相等,返回0;如果第一個參數小於第二個參數,返回一個負數。
過去,要在max()方法中使用用戶定義的對象,必須首先通過一個類顯式實現Comparator接口,然後創建該類的一個實例,通過這種方法獲得Comparator接口的一個實例,然後,把這個實例作為比較器傳遞給max()方法。在JDK 8中,現在可以簡單地將比較方法的引用傳遞給max()方法,因為這將自動實現比較器。)
class MyClass {
private int val;
MyClass(int v) {
val = v;
}
public int getValue() {
return val;
}
}
class UseMethodRef {
public static int compareMC(MyClass a, MyClass b) {
return a.getValue() - b.getValue();
}
public static void main(String[] args) {
ArrayList a1 = new ArrayList();
a1.add(new MyClass(1));
a1.add(new MyClass(4));
a1.add(new MyClass(2));
a1.add(new MyClass(9));
a1.add(new MyClass(3));
a1.add(new MyClass(7));
//UseMethodRef::compareMC生成了抽象接口Comparator定義的compare()方法的實例。
MyClass maxValObj = Collections.max(a1, UseMethodRef::compareMC);
System.out.println("Maximum value is: " + maxValObj.getValue());
}
}
輸出結果:
Maximum value is: 9
分析:
在程序中,注意MyClass即沒有定義自己的比較方法,也沒有實現Comparator接口。但是,通過調用max()方法,仍然可以獲得MyClass對象列表中的最大值,這是因為UseMethodRef定義了靜態方法compareMC(),它與Comparator定義的compare()方法兼容。因此,沒喲必要顯式的實現Comparator接口並創建其實例。
2)實例方法引用
這種語法與用於靜態方法的語法類似,只不過這裡使用對象引用而不是類名。
實例方法引用又分以下三種類型
a.實例上的實例方法引用
組成語法格式:instanceReference::methodName
Note:
對於具體(或者任意)對象的實例方法引用,在實例方法名稱和其所屬類型名稱間加上分隔符 :
與引用靜態方法引用相比,都換為實例對象的而已。
eg:
-- Function upper = String::toUpperCase;
--
/*
* 函數式接口
* */
interface StringFunc {
String func(String n);
}
class MyStringOps {
//普通方法: 反轉字符串
public String strReverse(String str) {
String result = "";
for (int i = str.length() - 1; i >= 0; i--) {
result += str.charAt(i);
}
return result;
}
}
class MethodRefDemo2 {
public static String stringOp(StringFunc sf, String s) {
return sf.func(s);
}
public static void main(String[] args) {
String inStr = "lambda add power to Java";
MyStringOps strOps = new MyStringOps();//實例對象
//MyStringOps::strReverse 相當於實現了接口方法func() ,並在接口方法func()中作了MyStringOps.strReverse()操作
String outStr = stringOp(strOps::strReverse, inStr);
System.out.println("Original string: " + inStr);
System.out.println("String reserved: " + outStr);
}
}
輸出結果:
Original string: lambda add power to Java
String reserved: avaJ ot rewop dda adbmal
分析:
這裡使用了類的名稱,而不是具體的對象,盡管指定的是實例方法。使用這種形式時,函數式接口的第一個參數匹配調用對象,第二個參數匹配方法指定的參數。
--定義了一個方法counter(),用於統計某個數組中,滿足函數式接口MyFunc的fun()方法定義的條件的對象個數。本例中,統計HighTemp類的實例個數。
interface MyFunc {
boolean func(T v1, T v2);
}
class HighTemp {
private int hTemp;
HighTemp(int ht) {
hTemp = ht;
}
public boolean sameTemp(HighTemp ht2) {
return hTemp == ht2.hTemp;
}
public boolean lessThanTemp(HighTemp ht2) {
return hTemp < ht2.hTemp;
}
}
class InstanceMethWithObjectRefDemo {
public static int counter(T[] vals, MyFunc f, T v) {
int count = 0;
for (int i = 0; i < vals.length; i++) {
if (f.func(vals[i], v)) count++;
}
return count;
}
public static void main(String[] args) {
int count;
HighTemp[] weekDayHighs = {
new HighTemp(89), new HighTemp(82),
new HighTemp(90), new HighTemp(89),
new HighTemp(89), new HighTemp(91),
new HighTemp(84), new HighTemp(83)};
//HighTemp::sameTemp 為實例方法引用
count = counter(weekDayHighs, HighTemp::sameTemp, new HighTemp(89));
System.out.println(count + " days had a high of 89");
HighTemp[] weekDayHighs2 = {
new HighTemp(31), new HighTemp(12),
new HighTemp(24), new HighTemp(19),
new HighTemp(18), new HighTemp(12),
new HighTemp(-1), new HighTemp(13)};
count = counter(weekDayHighs2, HighTemp::sameTemp, new HighTemp(12));
System.out.println(count + " days had a high of 12");
count = counter(weekDayHighs, HighTemp::lessThanTemp, new HighTemp(89));
System.out.println(count + " days had a high less than 89");
count = counter(weekDayHighs2, HighTemp::lessThanTemp, new HighTemp(19));
System.out.println(count + " days had a high of less than 19");
}
}
輸出結果:
3 days had a high of 89
2 days had a high of 12
3 days had a high less than 89
5 days had a hign less than 19
分析:
注意HighTemp有兩個實例方法:someTemp()和lessThanTemp()。如果兩個HighTemp對象包含相同的溫度,sameTemp()方法返回true。如果調用對象的溫度小於被傳遞的對象的溫度,lessThanTemp()方法返回true。這兩個方法都有一個HighTemp類型的參數,並且都返回布爾結果。因此,這兩個方法都與MyFunc函數式接口兼容,因為調用對象類型可以映射到func()的第一個參數,傳遞的實參可以映射到func()的第二個參數。因此,這個表達式:HighTemp::sameTemp
被傳遞給counter()方法時,會創建函數式接口的一個實例,其中第一個參數的參數類型就是實例方法的調用對象的類型,也就是HighTemp。第二個參數的類型也是HighTemp,因為這是sameTemp()方法的參數。對於lessThanTemp(),這也是成立的。
Note:
上面程序中函數式接口中的函數boolean func(T v1,T v2)中含有兩個參數,而HighTemp中函數sameTemp(HighTemp ht2)含有一個參數,但是能夠兼容的原因是:
其實HighTemp類中的sameTemp(HighTemp ht2)其實包含兩個參數,默認隱藏調用這個函數的引用this。
故,當使用類的實例方法作為方法引用時,函數式接口的第一個參數匹配類的實例方法的調用對象,第二個參數才匹配方法指定的參數。
b.超類上的實例方法引用
組成語法格式:super::methodName
方法的名稱由methodName指定
通過使用super,可以引用方法的超類版本。
eg:super::name
Note:還可以捕獲this 指針
this :: equals 等價於lambda表達式 x -> this.equals(x);
c.類型上的實例方法引用
組成語法格式:ClassName::methodName
Note:
若類型的實例方法是泛型的,就需要在::分隔符前提供類型參數,或者(多數情況下)利用目標類型推導出其類型。
靜態方法引用和類型上的實例方法引用擁有一樣的語法。編譯器會根據實際情況做出決定。
一般我們不需要指定方法引用中的參數類型,因為編譯器往往可以推導出結果,但如果需要我們也可以顯式在::分隔符之前提供參數類型信息。
eg:
String::toString等價於lambda表達式(s) -> s.toString()
這裡不太容易理解,實例方法要通過對象來調用,方法引用對應Lambda,Lambda的第一個參數會成為調用實例方法的對象。
在泛型類或泛型方法中,也可以使用方法引用。
interface MyFunc {
int func(T[] als, T v);
}
class MyArrayOps {
public static int countMatching(T[] vals, T v) {
int count = 0;
for (int i = 0; i < vals.length; i++) {
if (vals[i] == v) count++;
}
return count;
}
}
class GenericMethodRefDemo {
public static int myOp(MyFunc f, T[] vals, T v) {
return f.func(vals, v);
}
public static void main(String[] args){
Integer[] vals = {1, 2, 3, 4, 2, 3, 4, 4, 5};
String[] strs = {"One", "Two", "Three", "Two"};
int count;
count=myOp(MyArrayOps::countMatching, vals, 4);
System.out.println("vals contains "+count+" 4s");
count=myOp(MyArrayOps::countMatching, strs, "Two");
System.out.println("strs contains "+count+" Twos");
}
}
輸出結果:
vals contains 3 4s
strs contains 2 Twos
分析:
在程序中,MyArrayOps是非泛型類,包含泛型方法countMatching()。該方法返回數組中與指定值匹配的元素的個數。注意這裡如何指定泛型類型參數。例如,在main()方法中,對countMatching()方法的第一次調用如下所示:count = myOp(MyArrayOps::countMatching,vals,4);
這裡傳遞了類型參數Integer。
注意,參數傳遞發生在::的後面。這種語法可以推廣。當把泛型方法指定為方法引用時,類型參數出現在::之後、方法名之前。但是,需要指出的是,在這種情況(和其它許多情況)下,並非必須顯示指定類型參數,因為類型參數會被自動推斷得出。對於指定泛型類的情況,類型參數位於類名的後面::的前面。
3)構造方法引用
構造方法引用又分構造方法引用和數組構造方法引用。
a.構造方法引用 (也可以稱作構造器引用)
組成語法格式:Class::new
構造函數本質上是靜態方法,只是方法名字比較特殊,使用的是new 關鍵字。
eg:
-- String::new,等價於lambda表達式() -> new String()
--
List strings = new ArrayList();
strings.add("a");
strings.add("b");
Stream stream = strings.stream().map(Button::new);
List buttons = stream.collect(Collectors.toList());
--可以把這個引用賦值給定義的方法與構造函數兼容的任何函數式接口的引用
interface MyFunc {
MyClass func(int n);
}
class MyClass {
private int val;
MyClass(int v) {
val = v;
}
MyClass() {
val = 0;
}
public int getValue() {
return val;
}
}
class ConstructorRefDemo {
public static void main(String[] args) {
MyFunc myClassCons = MyClass::new;
MyClass mc = myClassCons.func(100);
System.out.println("val in mc is: " + mc.getValue());
}
}
輸出結果:
val in mc is: 100
b.數組構造方法引用:
組成語法格式:TypeName[]::new
eg:
--int[]::new 是一個含有一個參數的構造器引用,這個參數就是數組的長度。
等價於lambda表達式 x -> new int[x]。
--假想存在一個接收int參數的數組構造方法
IntFunction arrayMaker = int[]::new;
int[] array = arrayMaker.apply(10) // 創建數組 int[10]