糯米文學吧

位置:首頁 > 計算機 > java語言

Java方法引用是如何計算值的

java語言1.19W

Java如今已經是全球程式設計語言排名第一的語言,運用廣泛,前景廣闊,Java方法引用是如何計算值的?下面就一起來了解看看吧!

Java方法引用是如何計算值的

除了 lambda 表示式,Java SE 8 引入了方法引用作為簡寫符號。這些主要用於引用靜態方法(例如 Double :: toString)或建構函式(egString [] :: new),這些用法是直接的。 然而,對例項方法的方法引用會以令人驚訝的方式產生與lambda表示式不同的結果。 這是因為方法引用的呼叫目標(在 :: 之前的部分)在首次遇到它的宣告時被求值,而 lambda 表示式僅在實際呼叫時被求值。

  1呼叫例項方法

以下程式以各種方式呼叫例項方法,使用方法引用和 lambda 表示式來演示這種不同的行為。下面我們將通過輸出案例來看看發生了什麼。

class MethodRefTest {

public static void main(String[] args) {

tln("Constructor in method reference");

final Runnable newRef = new Counter()::show;

tln("Running...");

(); ();

tln("Constructor in lambda expression");

final Runnable newLambda = () -> new Counter()();

tln("Running...");

(); ();

tln("Factory in method reference");

final Runnable createRef = te()::show;

tln("Running...");

(); ();

tln("Factory in lambda expression");

final Runnable createLambda = () -> te()();

tln("Running...");

(); ();

tln("Variable in method reference");

obj = new Counter(); // NPE if after method reference declaration!

final Runnable varRef = obj::show;

tln("Running...");

(); obj = new Counter(); ();

tln("Variable in lambda expression");

obj = null; // no NPE, lambda expression declaration not evaluated

final Runnable varLambda = () -> ();

tln("Running...");

obj = new Counter(); ();

obj = new Counter(); ();

tln("Getter in method reference");

final Runnable getRef = get()::show;

tln("Running...");

(); obj = new Counter(); ();

tln("Getter in lambda expression");

final Runnable getLambda = () -> get()();

tln("Running...");

(); obj = new Counter(); ();

}

static Counter obj;

static Counter get() {

t("get: ");

return obj;

}

static class Counter {

static int count;

final int myCount;

Counter() {

myCount = count++;

tln(at("new Counter(%d)", myCount));

}

static Counter create() {

t("create: ");

return new Counter();

}

void show() {

tln(at("Counter(%d)()", myCount));

}

}

}

 2構造方法

第一個塊的程式碼在直接建立的 Counter 類的新例項上呼叫方法。 此類跟蹤在當前執行期間建立的例項數,並且通過其建立索引標識每個例項。 下面是輸出結果:

Constructor in method reference

new Counter(0)

Running...

Counter(0)()

Counter(0)()

Constructor in lambda expression

Running...

new Counter(1)

Counter(1)()

new Counter(2)

Counter(2)()

方法引用和 lambda 表示式呼叫一樣都被呼叫了兩次,正確的輸出了兩次 show 方法。 但是,在方法引用中,指定的構造方法只在宣告時呼叫了一次。 然後重用建立好的物件。該 lambda 表示式在宣告時不執行任何操作,而是在每次執行時呼叫建構函式。

3工廠方法

第二段程式碼實際上等同於第一段,但使用工廠方法而不是建構函式來獲取newCounter物件。 結果與以前相同,表明方法表示式的不同順序與在呼叫目標表達式中直接新建物件的順序無關。

通過方法引用來呼叫工廠方法

create: new Counter(3)

Running...

Counter(3)()

Counter(3)()

通過lambda 表示式呼叫工廠方法

Running...

create: new Counter(4)

Counter(4)()

create: new Counter(5)

Counter(5)()

 4變數訪問

第三段程式碼測試了(不同方式下的)變數訪問,這裡由於lambda表示式不接受可變的本地變數而使用了靜態欄位.

使用方法引用的變數訪問

new Counter(6)

Running...

Counter(6)()

new Counter(7)

Counter(6)()

使用lambda表示式的變數訪問

Running...

new Counter(8)

Counter(8)()

new Counter(9)

Counter(9)()

方法引用對它的呼叫目標立即求值會造成兩個結果.

一是,欄位初始化必須在(方法引用)宣告前,否則會發生da表示式卻不是這種情況:我們可以在(lambda表示式)宣告前將欄位重置為null---只要我們在呼叫時該欄位有有效值即可.

二是,由於目標物件的引用儲存的是欄位的即時值,所以當該欄位接下來被賦值為一個新建的Counter,目標物件的引用也不會變.於此不同地是,lambda表示式每次執行時都會去取欄位當前的值.

5取值器(Getter)方法

最後一段程式碼與變數訪問的`例子等效,只是使用了getter方法來讀取欄位當前的值.又一次,在後面這個例子中,這個輔助字元再兩次呼叫之間發生了改變.

使用方法引用的Getter

get: Running...

Counter(9)()

new Counter(10)

Counter(9)()

使用lambda表示式的Getter

Running...

get: Counter(10)()

new Counter(11)

get: Counter(11)()

在方法引用的例子中,get:在Running...之前只出現了一次,說明getter在宣告的時候只調用了一次.因此,返回的欄位值被用於兩次show方法呼叫da表示式呼叫了兩次getter,在第二次呼叫時獲取到了更新的欄位值.

 分析

上述行為在Java SE 8 語言規範的§15.13.3 “方法引用的執行時求值”的最後註釋中被描述:

方法引用表示式求值的時機比lambda表示式更復雜(§15.27.4).當一個方法引用表示式在:: 分隔符前有一個表示式(而不是一個型別)時,這個子表示式將會被立即求值.求值的結果會被一直儲存,直到相關的函式介面型別被呼叫;那個時候,求值的結果將會被用作呼叫的目標引用.這表明在::分隔符之前的表示式只在程式進入方法引用表示式時求值,並且不會在接下來的函式介面型別呼叫中重新求值.

 發現

我第一次注意到方法引用與lambda表示式的不同是當我像在建構函式測試例子中那樣嘗試使用方法引用在一個MenuItem 處理器中建立對話方塊例項時.我驚訝地發現每次呼叫會開啟帶著上次調所有控制內容的完全一樣的例項.使用lambda表示式替換方法引用後才產生期望的行為----每次呼叫建立一個新的對話方塊.

方法引用的立即對目標求值很有可能不是使用者想要的行為,在大多數情況下,我們期望目標在呼叫間改變.你可以考慮只對靜態方法和建構函式(X::new)使用方法引用,或者和那些對所有呼叫都確定不會改變的例項引用一起使用.如果目標引用有任何需要動態重新求值的可能,你就必須使用lambda表示式.

標籤:JAVA 引用 計算