糯米文學吧

位置:首頁 > 計算機 > 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 引用 計算