- 相關(guān)推薦
Java方法引用是如何計算值的
Java如今已經(jīng)是全球編程語言排名第一的語言,運用廣泛,前景廣闊,Java方法引用是如何計算值的?下面就一起來了解看看吧!
除了 lambda 表達式,Java SE 8 引入了方法引用作為簡寫符號。這些主要用于引用靜態(tài)方法(例如 Double :: toString)或構(gòu)造函數(shù)(egString [] :: new),這些用法是直接的。 然而,對實例方法的方法引用會以令人驚訝的方式產(chǎn)生與lambda表達式不同的結(jié)果。 這是因為方法引用的調(diào)用目標(在 :: 之前的部分)在首次遇到它的聲明時被求值,而 lambda 表達式僅在實際調(diào)用時被求值。
1調(diào)用實例方法
以下程序以各種方式調(diào)用實例方法,使用方法引用和 lambda 表達式來演示這種不同的行為。下面我們將通過輸出案例來看看發(fā)生了什么。
class MethodRefTest {
public static void main(String[] args) {
System.out.println(" Constructor in method reference");
final Runnable newRef = new Counter()::show;
System.out.println("Running...");
newRef.run(); newRef.run();
System.out.println(" Constructor in lambda expression");
final Runnable newLambda = () -> new Counter().show();
System.out.println("Running...");
newLambda.run(); newLambda.run();
System.out.println(" Factory in method reference");
final Runnable createRef = Counter.create()::show;
System.out.println("Running...");
createRef.run(); createRef.run();
System.out.println(" Factory in lambda expression");
final Runnable createLambda = () -> Counter.create().show();
System.out.println("Running...");
createLambda.run(); createLambda.run();
System.out.println(" Variable in method reference");
obj = new Counter(); // NPE if after method reference declaration!
final Runnable varRef = obj::show;
System.out.println("Running...");
varRef.run(); obj = new Counter(); varRef.run();
System.out.println(" Variable in lambda expression");
obj = null; // no NPE, lambda expression declaration not evaluated
final Runnable varLambda = () -> obj.show();
System.out.println("Running...");
obj = new Counter(); varLambda.run();
obj = new Counter(); varLambda.run();
System.out.println(" Getter in method reference");
final Runnable getRef = get()::show;
System.out.println("Running...");
getRef.run(); obj = new Counter(); getRef.run();
System.out.println(" Getter in lambda expression");
final Runnable getLambda = () -> get().show();
System.out.println("Running...");
getLambda.run(); obj = new Counter(); getLambda.run();
}
static Counter obj;
static Counter get() {
System.out.print("get: ");
return obj;
}
static class Counter {
static int count;
final int myCount;
Counter() {
myCount = count++;
System.out.println(String.format("new Counter(%d)", myCount));
}
static Counter create() {
System.out.print("create: ");
return new Counter();
}
void show() {
System.out.println(String.format("Counter(%d).show()", myCount));
}
}
}
2構(gòu)造方法
第一個塊的代碼在直接創(chuàng)建的 Counter 類的新實例上調(diào)用方法。 此類跟蹤在當(dāng)前運行期間創(chuàng)建的實例數(shù),并且通過其創(chuàng)建索引標識每個實例。 下面是輸出結(jié)果:
Constructor in method reference
new Counter(0)
Running...
Counter(0).show()
Counter(0).show()
Constructor in lambda expression
Running...
new Counter(1)
Counter(1).show()
new Counter(2)
Counter(2).show()
方法引用和 lambda 表達式調(diào)用一樣都被調(diào)用了兩次,正確的輸出了兩次 show 方法。 但是,在方法引用中,指定的構(gòu)造方法只在聲明時調(diào)用了一次。 然后重用創(chuàng)建好的對象。該 lambda 表達式在聲明時不執(zhí)行任何操作,而是在每次運行時調(diào)用構(gòu)造函數(shù)。
3工廠方法
第二段代碼實際上等同于第一段,但使用工廠方法而不是構(gòu)造函數(shù)來獲取newCounter對象。 結(jié)果與以前相同,表明方法表達式的不同順序與在調(diào)用目標表達式中直接新建對象的順序無關(guān)。
通過方法引用來調(diào)用工廠方法
create: new Counter(3)
Running...
Counter(3).show()
Counter(3).show()
通過lambda 表達式調(diào)用工廠方法
Running...
create: new Counter(4)
Counter(4).show()
create: new Counter(5)
Counter(5).show()
4變量訪問
第三段代碼測試了(不同方式下的)變量訪問,這里由于lambda表達式不接受可變的本地變量而使用了靜態(tài)字段.
使用方法引用的變量訪問
new Counter(6)
Running...
Counter(6).show()
new Counter(7)
Counter(6).show()
使用lambda表達式的變量訪問
Running...
new Counter(8)
Counter(8).show()
new Counter(9)
Counter(9).show()
方法引用對它的調(diào)用目標立即求值會造成兩個結(jié)果.
一是,字段初始化必須在(方法引用)聲明前,否則會發(fā)生NullPointerException.lambda表達式卻不是這種情況:我們可以在(lambda表達式)聲明前將字段重置為null---只要我們在調(diào)用時該字段有有效值即可.
二是,由于目標對象的引用保存的是字段的即時值,所以當(dāng)該字段接下來被賦值為一個新建的Counter,目標對象的引用也不會變.于此不同地是,lambda表達式每次運行時都會去取字段當(dāng)前的值.
5取值器(Getter)方法
最后一段代碼與變量訪問的例子等效,只是使用了getter方法來讀取字段當(dāng)前的值.又一次,在后面這個例子中,這個輔助字符再兩次調(diào)用之間發(fā)生了改變.
使用方法引用的Getter
get: Running...
Counter(9).show()
new Counter(10)
Counter(9).show()
使用lambda表達式的Getter
Running...
get: Counter(10).show()
new Counter(11)
get: Counter(11).show()
在方法引用的例子中,get:在Running...之前只出現(xiàn)了一次,說明getter在聲明的時候只調(diào)用了一次.因此,返回的字段值被用于兩次show方法調(diào)用.lambda表達式調(diào)用了兩次getter,在第二次調(diào)用時獲取到了更新的字段值.
分析
上述行為在Java SE 8 語言規(guī)范的§15.13.3 “方法引用的運行時求值”的最后注釋中被描述:
方法引用表達式求值的時機比lambda表達式更復(fù)雜(§15.27.4).當(dāng)一個方法引用表達式在:: 分隔符前有一個表達式(而不是一個類型)時,這個子表達式將會被立即求值.求值的結(jié)果會被一直保存,直到相關(guān)的函數(shù)接口類型被調(diào)用;那個時候,求值的結(jié)果將會被用作調(diào)用的目標引用.這表明在::分隔符之前的表達式只在程序進入方法引用表達式時求值,并且不會在接下來的函數(shù)接口類型調(diào)用中重新求值.
發(fā)現(xiàn)
我第一次注意到方法引用與lambda表達式的不同是當(dāng)我像在構(gòu)造函數(shù)測試例子中那樣嘗試使用方法引用在一個MenuItem 處理器中創(chuàng)建對話框?qū)嵗龝r.我驚訝地發(fā)現(xiàn)每次調(diào)用會打開帶著上次調(diào)所有控制內(nèi)容的完全一樣的實例.使用lambda表達式替換方法引用后才產(chǎn)生期望的行為----每次調(diào)用創(chuàng)建一個新的對話框.
方法引用的立即對目標求值很有可能不是用戶想要的行為,在大多數(shù)情況下,我們期望目標在調(diào)用間改變.你可以考慮只對靜態(tài)方法和構(gòu)造函數(shù)(X::new)使用方法引用,或者和那些對所有調(diào)用都確定不會改變的實例引用一起使用.如果目標引用有任何需要動態(tài)重新求值的可能,你就必須使用lambda表達式.
【Java方法引用是如何計算值的】相關(guān)文章:
java傳值的方法06-24
Java數(shù)組特定值高效判斷方法10-03
如何正確實現(xiàn)Java中的hashCode方法08-08
從Java的jar文件中如何讀取數(shù)據(jù)的方法10-18
java虛方法09-21
java入門方法10-13
java調(diào)用的方法09-04
java方法重寫的方法分析09-04