2014-06-07 66 views
62

假設我有一個使用lambda表達式(閉包)定義的對象列表。有沒有辦法檢查它們,以便比較它們?有沒有辦法比較lambdas?

我最感興趣的代碼是

List<Strategy> strategies = getStrategies(); 
    Strategy a = (Strategy) this::a; 
    if (strategies.contains(a)) { // ... 

完整的代碼

import java.util.Arrays; 
import java.util.List; 

public class ClosureEqualsMain { 
    interface Strategy { 
     void invoke(/*args*/); 
     default boolean equals(Object o) { // doesn't compile 
      return Closures.equals(this, o); 
     } 
    } 

    public void a() { } 
    public void b() { } 
    public void c() { } 

    public List<Strategy> getStrategies() { 
     return Arrays.asList(this::a, this::b, this::c); 
    } 

    private void testStrategies() { 
     List<Strategy> strategies = getStrategies(); 
     System.out.println(strategies); 
     Strategy a = (Strategy) this::a; 
     // prints false 
     System.out.println("strategies.contains(this::a) is " + strategies.contains(a)); 
    } 

    public static void main(String... ignored) { 
     new ClosureEqualsMain().testStrategies(); 
    } 

    enum Closures {; 
     public static <Closure> boolean equals(Closure c1, Closure c2) { 
      // This doesn't compare the contents 
      // like others immutables e.g. String 
      return c1.equals(c2); 
     } 

     public static <Closure> int hashCode(Closure c) { 
      return // a hashCode which can detect duplicates for a Set<Strategy> 
     } 

     public static <Closure> String asString(Closure c) { 
      return // something better than Object.toString(); 
     } 
    }  

    public String toString() { 
     return "my-ClosureEqualsMain"; 
    } 
} 

這樣看來,唯一的解決辦法是定義每個lambda作爲一個字段,只使用這些字段。如果您想打印出所調用的方法,最好使用Method。用lambda表達式有更好的方法嗎?

另外,是否可以打印一個lambda並獲得一些人類可讀的東西?如果您打印this::a,而不是

ClosureEqualsMain$$Lambda$1/[email protected] 

得到類似

ClosureEqualsMain.a() 

甚至使用this.toString和方法。

my-ClosureEqualsMain.a(); 
+1

您可以在閉包中定義toString,equals和hashhCode方法。 –

+0

@AnkitZalani你可以給一個編譯的例子嗎? –

+0

@PeterLawrey,由於在Object中定義了toString,所以我認爲你可以定義一個接口來提供'toString'的默認實現,而不會違反* single-method *接口的功能。我沒有檢查過這個。 –

回答

59

這個問題可以解釋爲相對於規範或實現。顯然,實現可能會發生變化,但是如果發生這種情況,您可能會重新編寫代碼,因此我會在兩者中進行回答。

這也取決於你想要做什麼。你正在尋找優化,還是你正在尋找ironclad保證兩個實例是(或不是)相同的功能? (如果是後者,你會發現自己與計算物理學不一致,因爲即使問題與詢問兩個函數是否計算相同的事物一樣簡單也是不可取的。)

從規範的角度來看,語言規範承諾只有評估(不調用)lambda表達式的結果是實現目標功能接口的類的實例。它對結果的身份或別名程度沒有任何承諾。這是爲了給予實現最大的靈活性以提供更好的性能(這就是lambda可以比內部類更快的速度;我們並不束縛於「必須創建唯一實例」約束,即內部類)

所以基本上,規範並沒有給你太多,除非顯然兩個引用相等(==)的lambda將計算相同的函數。

從實施的角度來看,您可以總結一點。目前,可能會改變實現lambdas的合成類與程序中的捕獲站點之間的1:1關係。因此,捕獲「x - > x + 1」的兩個單獨的代碼位可能會映射到不同的類。但是,如果您在相同的捕獲站點評估相同的lambda,並且lambda不捕獲,則會得到相同的實例,可以將其與參考相等進行比較。

如果lambda表達式是序列化的,他們會更輕易放棄自己的狀態,以換取犧牲一些性能和安全性(沒有免費的午餐。)

一個領域是它可能是不切實際的調整定義平等是與方法引用,因爲這將使他們能夠被用作監聽器並被正確註銷。這正在考慮之中。

我認爲你試圖去的是什麼:如果兩個lambda表達式轉換爲相同的功能接口,是由相同的行爲函數來表示,並具有相同的捕獲指定參數時,它們是相同的

不幸的是,這是很難做到的(對於不可序列化的lambda表達式,你不能獲得所有的組件)並且不夠(因爲兩個單獨編譯的文件可以將相同的lambda轉換爲相同的函數接口類型,無法分辨。)

EG討論了是否公開足夠的信息來做出這些判斷,以及討論lambda是否應該實現更多選擇等於/ hashCode或更多描述toString。結論是,我們不願意支付任何性能成本來向調用者提供這些信息(壞的折衷,懲罰99.99%的用戶獲得0.01%的好處)。

關於toString的確切結論還沒有達成,但還是留待未來重新審視。但是,在這個問題上雙方都有一些很好的論點。這不是一個扣籃。

+0

+1雖然我明白支持'=='的平等是一個難以解決的問題,但我一直認爲會有簡單的情況,即編譯器,如果不是JVM可以識別出在一行上的this :: a是在另一行上與'this :: a'相同。實際上,對於我來說,通過給每個呼叫站點實現它,對你而言都是不明顯的。也許他們可以進行不同的優化,但我會認爲內聯可以做到這一點。 –

+0

在任何情況下,你都有一個綁定的對象和一個方法來調用,在這種簡單的情況下,這對於平等來說已經足夠了,但是我明白一般的解決方案很難,而不一致的行爲確實會引起混淆,例如整數緩存意味着一些自動裝箱的引用是==而其他的則需要equals()。 –

+1

與數組的'Array'和'Arrays'實用程序claases類似,因爲它們無法獲得像樣的equals,hashCode或toString,所以我可以想象一天有'Closures'實用程序類。由於有些語言可以打印和排列並查看其內容,所以我想有些語言可以打印閉包,並瞭解閉包的作用。 (可能是一串代碼更好,但對某些人來說不太令人滿意) –

6

我沒有看到從封閉本身獲取這些信息的可能性。 關閉不提供狀態。

但是如果你想檢查和比較方法,你可以使用Java-Reflection。 當然,這並不是一個非常漂亮的解決方案,因爲性能和例外,這些都是可以接受的。但這樣你就可以獲得這些元信息。

+0

+1反射允許我將'this'作爲'arg $ 1',但不能比較所稱的方法。我可能需要讀取字節碼以查看它是否相同。 –

5

爲了比較labmdas我通常讓接口擴展Serializable然後比較序列化的字節。不是很好,但適用於大多數情況。

相關問題