簡體   English   中英

為什么Long.valueOf(0).equals(Integer.valueOf(0))為false?

[英]Why is Long.valueOf(0).equals(Integer.valueOf(0)) false?

這個問題是由奇怪的HashMap.put()行為引起的

我想我明白為什么Map<K,V>.put采用KMap<K,V>.get采用一個Object ,它似乎沒有這樣做會破壞太多的現有代碼。

現在我們陷入了一個容易出錯的場景:

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(5L,"Five"); // compiler barfs on m.put(5, "Five")
m.contains(5); // no complains from compiler, but returns false

如果Long值在int范圍內並且值相等,那么返回true是不是可以解決這個問題?

這是Long.java的源代碼

public boolean equals(Object obj) {
    if (obj instanceof Long) {
        return value == ((Long)obj).longValue();
    }
    return false;
}

即它必須是Long類型才能相等。 我認為之間的關鍵區別是:

long l = 42L
int i = 42;
l == i

並且上面的示例是使用基元可以發生int值的隱式加寬,但是對於對象類型,沒有用於從Integer隱式轉換為Long的規則。

另外看看Java Puzzlers ,它有很多類似的例子。

一般來說,盡管在equals()的約定中沒有嚴格表達,但是對象不應該認為自己等於不是完全相同類的另一個對象(即使它是一個子類)。 考慮對稱屬性 - 如果a.equals(b)為真,那么b.equals(a)也必須為真。

讓我們有兩個對象,類Super fooSub類的bar ,它擴展了Super 現在考慮在Super中實現equals() ,特別是當它被稱為foo.equals(bar) Foo只知道bar是強類型的Object ,所以要獲得准確的比較,需要檢查它是Super的實例,如果不是則返回false。 是的,所以這部分很好。 它現在比較所有實例字段等(或實際的比較實現),並找到它們相等。 到現在為止還挺好。

但是,根據合約,如果知道bar.equals(foo)也將返回true,則它只能返回true。 由於bar可以是Super的任何子類,因此不清楚equals()方法是否會被覆蓋(如果可能的話)。 因此,為了確保您的實現是正確的,您需要對稱地編寫它並確保兩個對象是同一個類。

更基本的是,不同類的對象實際上不能被認為是相等的 - 因為在這種情況下,例如,只有其中一個可以插入到HashSet<Sub>

是的,但這一切都取決於比較算法以及轉換的距離。 例如,當你嘗試m.Contains("5")時,你想要發生什么? 或者如果你傳遞一個數組,其中5作為第一個元素? 簡單地說,它似乎是“如果類型不同,鍵是不同的”。

然后以object作為鍵來獲取集合。 如果你put 5L ,然后嘗試獲得5"5" ,...,你想要發生什么? 如果你put一個5L和一個5以及一個"5"並想要檢查一個5F怎么辦?

由於它是一個通用集合(或模板化,或任何你想稱之為),它必須檢查並對某些值類型進行一些特殊的比較。 如果K是int則檢查傳遞的對象是longshortfloatdouble ,...,然后轉換和比較。 如果K是float那么檢查傳遞的對象是否...

你明白了。

如果類型不匹配,另一種實現可能是拋出異常,但我經常希望它能做到。

你的問題看起來似乎是合理的,但是如果不是它的合同,它將違反equals()的一般約定,對於兩種不同的類型返回true。

與C ++不同,Java語言設計的一部分是對象永遠不會隱式轉換為其他類型。 這是使Java成為一種簡單易懂的語言的一部分。 C ++復雜性的一個合理部分來自隱式轉換及其與其他功能的交互。

此外,Java在基元和對象之間具有明顯且可見的二分法。 這與其他語言不同,在這些語言中,這種差異作為優化隱藏在幕后。 這意味着你不能指望Long和Integer像long和int那樣行事。

可以編寫庫代碼來隱藏這些差異,但這實際上可能會使編程環境不那么一致。

所以你的代碼應該是....

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(5L, "Five"); // compiler barfs on m.put(5, "Five")
System.out.println(m.containsKey(5L)); // true

你忘了java是自動裝箱你的代碼,所以上面的代碼將是equivelenet

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(new Long(5L), "Five"); // compiler barfs on m.put(5, "Five")
System.out.println(m.containsKey(new Long(5))); // true
System.out.println(m.containsKey(new Long(5L))); // true

所以問題的一部分是自動裝箱。 另一部分是你有其他海報所說的不同類型。

其他答案充分解釋了它失敗的原因,但沒有一個解決如何編寫在此問題上不易出錯的代碼。 必須記住添加類型轉換(沒有編譯器幫助),后綴基元與L等等是不可接受的恕我直言。

我強烈建議在使用原語時(以及許多其他情況下)使用GNU trove庫集合。 例如,有一個TLongLongHashMap可以將內容存儲為原始long。 因此,您永遠不會以拳擊/拆箱結束,並且永遠不會遇到意外行為:

TLongLongHashMap map = new TLongLongHashMap();
map.put(1L, 45L);
map.containsKey(1); // returns true, 1 gets promoted to long from int by compiler
int x = map.get(1); // Helpful compiler error. x is not a long
int x = (int)map.get(1); // OK. cast reassures compiler that you know
long x = map.get(1); // Better.

等等。 沒有必要讓類型正確,如果你做了一些愚蠢的事情(嘗試在int中存儲long),編譯器會給你一個錯誤(你可以糾正或覆蓋)。

自動投射的規則意味着比較也能正常工作:

if(map.get(1) == 45) // 1 promoted to long, 45 promoted to long...all is well

作為獎勵,內存開銷和運行時性能要好得多。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM