2017-11-25 140 views
0

我剛剛在一本書中讀到,當一個lambda表達式被分配給一個函數接口時,那麼它會爲lambda表達式和該類型的一個實例設置「target type」(即,功能接口的類型)是使用作爲功能接口中抽象方法實現的lambda表達式創建的。Java中的Lambda表達式機制

我的問題:如果是這樣,那麼是否意味着lambda表達式是不是真的獨立的方法。作爲這樣的新類型的元素帶入語言,而只是簡單地表達一個匿名類更緊湊的方式因此僅僅在編譯器方面增加了一些功能(就像泛型一樣)?

此外,方法引用如何符合那個,特別是與任何對象沒有關聯的靜態方法?例如,當一個方法對一個實例方法的引用被分配給一個功能接口時,那麼將使用該方法的封裝對象,但是在靜態方法的情況下會發生什麼 - 它們不與任何對象相關聯。

+0

的JLS上lambda表達式[第15.27](https://docs.oracle.com/javase/specs/jls/se9/html/jls-15.html#jls-15.27)和[段15.13(HTTPS ://docs.oracle.com/javase/specs/jls/se9/html/jls-15.html#jls-15.13)方法引用爲您提供了答案。 –

回答

1

如果是這樣,那麼是否意味着lambda表達式是不是真的獨立的方法。作爲這樣的新類型的元素帶入語言,

正確,lambda表達式被編譯成一個合成常規方法名稱

但是隻是表達一個匿名類的更簡潔的方式,因此僅僅是在編譯器端添加設施(就像泛型一樣)?

不,它不僅在編譯器方面。在涉及的JVM中也有代碼,因此編譯器不必爲lambda表編寫類文件。

此外,方法引用如何符合那個,特別是與任何對象沒有關聯的靜態方法?

方法引用與lambda表達式沒有什麼不同:在運行時,必須有一個實現函數接口的對象。在調用對象的「SAM」時,此方法將調用引用的方法。

例如,當一個方法參考實例方法被分配給一個功能接口然後被用於該方法中的對象封裝,

不,它不能被使用。讓我們用System.out::println方法參考採取下面的例子:

Arrays.asList("A", "B").forEach(System.out::println); 

List<E>.forEach()期望一個Consumer<? super E>限定方法void accept(E e)。編譯器需要在類文件中生成字節代碼和其他信息,以便在運行時JVM可以使用方法void accept(E e)生成一個實現Consumer<E>的類。這個生成的方法然後調用System.out.println(Object o)

運行時生成的類會看起來像

class $$lambda$xy implements Consumer<Object> { 
    private PrintStream out; 

    $$lambda$xy(PrintStream out) { 
     this.out = out; 
    } 

    void accept(Object o) { 
     out.println(o); 
    } 
} 

你在徵求意見的問題:「爲什麼不直接分配到實例及其方法?」

讓我們擴展的例子一點點:

static void helloWorld(Consumer<String> consumer) { 
    consumer.apply("Hello World!"); 
} 

public static void main(String[] args) { 
    helloWorld(System.out::println); 
} 

要編譯,編譯器已經生成的字節碼,創建實施Consumer<String>對象(因此它可以傳遞對象爲helloWorld())。這個對象某種程度上具有存儲在調用它的accept(x)方法有調用println(x)System.out PrintStream的信息。

其他語言可能有其他名稱或概念的這種對象 - 在Java中建立的概念是「匿名類實現接口和匿名類的對象」。

如何對象存儲這些信息?那麼,你可以發明一些超酷的新方式來存儲這些信息。 Java語言設計師決定,一個匿名類將會足夠好 - 暫時。但他們有先見之明,如果有人提出一個更有效的新思路來實現它,這應該很容易集成到Java生態系統(Java編譯器和JVM)中。

所以,他們也決定創建匿名類在編譯時間,但讓編譯器只寫了必要的信息在類文件。現在JVM可以在運行時確定存儲信息的最佳方式(在正確的對象上調用正確的方法)是什麼。

+0

感謝您的詳細回覆。但我仍然有一個很難理解的是:爲什麼會「的JVM生成一個類實現消費者與的方法無效接受(E E)」時,其實施方法是類要調用實例(通過它的實際對象)來執行功能界面的合同嗎?爲什麼不直接分配給實例及其方法?你相信這是因爲實例沒有裝飾性地實現接口? – Learnerer

+0

@Learnerer我已經更新了我的答案,希望這可以幫助 –

+0

也許混淆是由於編程模型和實現機制之間的干擾。我很感謝你的回答。 – Learnerer

0

例如,當一個方法參考實例方法被分配 到功能接口,則使用該 方法封裝的對象,但在靜態方法中的情況下,會發生什麼 - 那些不與任何物體相關聯。

這取決於上下文。假設我們有一個靜態的Utils#trim(String)方法,它明顯會修剪給定的字符串。

現在,最好有一個List<String> list並讓它有一些字符串。我們可以這樣做:

list.stream().map(Utils::trim).collect(Collectors.toList());

正如您所看到的,在給定的上下文中,我們使用lambda靜態方法參考,以便使用列表中的每個字符串作爲Utils::trim方法的輸入參數。

+0

謝謝。但我真的不問如何使用lambdas或方法引用,我只是想弄清楚幕後的工作 - 它們是如何實現的 – Learnerer