- 相關(guān)推薦
Java方法引用是如何計(jì)算值的
Java如今已經(jīng)是全球編程語言排名第一的語言,運(yùn)用廣泛,前景廣闊,Java方法引用是如何計(jì)算值的?下面就一起來了解看看吧!
除了 lambda 表達(dá)式,Java SE 8 引入了方法引用作為簡寫符號(hào)。這些主要用于引用靜態(tài)方法(例如 Double :: toString)或構(gòu)造函數(shù)(egString [] :: new),這些用法是直接的。 然而,對(duì)實(shí)例方法的方法引用會(huì)以令人驚訝的方式產(chǎn)生與lambda表達(dá)式不同的結(jié)果。 這是因?yàn)榉椒ㄒ玫恼{(diào)用目標(biāo)(在 :: 之前的部分)在首次遇到它的聲明時(shí)被求值,而 lambda 表達(dá)式僅在實(shí)際調(diào)用時(shí)被求值。
1調(diào)用實(shí)例方法
以下程序以各種方式調(diào)用實(shí)例方法,使用方法引用和 lambda 表達(dá)式來演示這種不同的行為。下面我們將通過輸出案例來看看發(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)造方法
第一個(gè)塊的代碼在直接創(chuàng)建的 Counter 類的新實(shí)例上調(diào)用方法。 此類跟蹤在當(dāng)前運(yùn)行期間創(chuàng)建的實(shí)例數(shù),并且通過其創(chuàng)建索引標(biāo)識(shí)每個(gè)實(shí)例。 下面是輸出結(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 表達(dá)式調(diào)用一樣都被調(diào)用了兩次,正確的輸出了兩次 show 方法。 但是,在方法引用中,指定的構(gòu)造方法只在聲明時(shí)調(diào)用了一次。 然后重用創(chuàng)建好的對(duì)象。該 lambda 表達(dá)式在聲明時(shí)不執(zhí)行任何操作,而是在每次運(yùn)行時(shí)調(diào)用構(gòu)造函數(shù)。
3工廠方法
第二段代碼實(shí)際上等同于第一段,但使用工廠方法而不是構(gòu)造函數(shù)來獲取newCounter對(duì)象。 結(jié)果與以前相同,表明方法表達(dá)式的不同順序與在調(diào)用目標(biāo)表達(dá)式中直接新建對(duì)象的順序無關(guān)。
通過方法引用來調(diào)用工廠方法
create: new Counter(3)
Running...
Counter(3).show()
Counter(3).show()
通過lambda 表達(dá)式調(diào)用工廠方法
Running...
create: new Counter(4)
Counter(4).show()
create: new Counter(5)
Counter(5).show()
4變量訪問
第三段代碼測(cè)試了(不同方式下的)變量訪問,這里由于lambda表達(dá)式不接受可變的本地變量而使用了靜態(tài)字段.
使用方法引用的變量訪問
new Counter(6)
Running...
Counter(6).show()
new Counter(7)
Counter(6).show()
使用lambda表達(dá)式的變量訪問
Running...
new Counter(8)
Counter(8).show()
new Counter(9)
Counter(9).show()
方法引用對(duì)它的調(diào)用目標(biāo)立即求值會(huì)造成兩個(gè)結(jié)果.
一是,字段初始化必須在(方法引用)聲明前,否則會(huì)發(fā)生NullPointerException.lambda表達(dá)式卻不是這種情況:我們可以在(lambda表達(dá)式)聲明前將字段重置為null---只要我們?cè)谡{(diào)用時(shí)該字段有有效值即可.
二是,由于目標(biāo)對(duì)象的引用保存的是字段的即時(shí)值,所以當(dāng)該字段接下來被賦值為一個(gè)新建的Counter,目標(biāo)對(duì)象的引用也不會(huì)變.于此不同地是,lambda表達(dá)式每次運(yùn)行時(shí)都會(huì)去取字段當(dāng)前的值.
5取值器(Getter)方法
最后一段代碼與變量訪問的例子等效,只是使用了getter方法來讀取字段當(dāng)前的值.又一次,在后面這個(gè)例子中,這個(gè)輔助字符再兩次調(diào)用之間發(fā)生了改變.
使用方法引用的Getter
get: Running...
Counter(9).show()
new Counter(10)
Counter(9).show()
使用lambda表達(dá)式的Getter
Running...
get: Counter(10).show()
new Counter(11)
get: Counter(11).show()
在方法引用的例子中,get:在Running...之前只出現(xiàn)了一次,說明getter在聲明的時(shí)候只調(diào)用了一次.因此,返回的字段值被用于兩次show方法調(diào)用.lambda表達(dá)式調(diào)用了兩次getter,在第二次調(diào)用時(shí)獲取到了更新的字段值.
分析
上述行為在Java SE 8 語言規(guī)范的§15.13.3 “方法引用的運(yùn)行時(shí)求值”的最后注釋中被描述:
方法引用表達(dá)式求值的時(shí)機(jī)比lambda表達(dá)式更復(fù)雜(§15.27.4).當(dāng)一個(gè)方法引用表達(dá)式在:: 分隔符前有一個(gè)表達(dá)式(而不是一個(gè)類型)時(shí),這個(gè)子表達(dá)式將會(huì)被立即求值.求值的結(jié)果會(huì)被一直保存,直到相關(guān)的函數(shù)接口類型被調(diào)用;那個(gè)時(shí)候,求值的結(jié)果將會(huì)被用作調(diào)用的目標(biāo)引用.這表明在::分隔符之前的表達(dá)式只在程序進(jìn)入方法引用表達(dá)式時(shí)求值,并且不會(huì)在接下來的函數(shù)接口類型調(diào)用中重新求值.
發(fā)現(xiàn)
我第一次注意到方法引用與lambda表達(dá)式的不同是當(dāng)我像在構(gòu)造函數(shù)測(cè)試?yán)又心菢訃L試使用方法引用在一個(gè)MenuItem 處理器中創(chuàng)建對(duì)話框?qū)嵗龝r(shí).我驚訝地發(fā)現(xiàn)每次調(diào)用會(huì)打開帶著上次調(diào)所有控制內(nèi)容的完全一樣的實(shí)例.使用lambda表達(dá)式替換方法引用后才產(chǎn)生期望的行為----每次調(diào)用創(chuàng)建一個(gè)新的對(duì)話框.
方法引用的立即對(duì)目標(biāo)求值很有可能不是用戶想要的行為,在大多數(shù)情況下,我們期望目標(biāo)在調(diào)用間改變.你可以考慮只對(duì)靜態(tài)方法和構(gòu)造函數(shù)(X::new)使用方法引用,或者和那些對(duì)所有調(diào)用都確定不會(huì)改變的實(shí)例引用一起使用.如果目標(biāo)引用有任何需要?jiǎng)討B(tài)重新求值的可能,你就必須使用lambda表達(dá)式.
【Java方法引用是如何計(jì)算值的】相關(guān)文章:
java傳值的方法06-24
Java數(shù)組特定值高效判斷方法10-03
如何正確實(shí)現(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