2009-01-15 137 views
16

此問題是由strange HashMap.put() behaviourLong.valueOf(0).equals(Integer.valueOf(0))爲什麼是false?

促使我想我明白爲什麼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值withing int範圍和值相等返回true解決了嗎?

回答

24

下面是從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值的隱式擴大,可能會發生,但與對象類型有從整數隱式轉換爲龍沒有規則。

也檢查出Java Puzzlers,它有很多類似這樣的例子。

+1

也許我不清楚。我知道爲什麼它會像源代碼那樣發生,我在發佈前閱讀過代碼。我的問題是爲什麼它決定應該這樣? – 2009-01-15 08:53:19

+3

http://stackoverflow.com/questions/445990/why-is-long-valueof0-equalsinteger-valueof0-false#446911更正確地解釋它。由於將Long與Integer比較可能會違反對稱性。 – 2009-01-15 20:44:04

5

是的,但這一切都歸結於比較算法以及轉換的距離。例如,當您嘗試m.Contains("5")時,您希望發生什麼?或者如果你傳遞一個5作爲第一個元素的數組?簡而言之,它似乎是連接起來的「如果類型不同,鍵是不同的」。

然後以object爲關鍵集合。如果你put a 5L,你想要發生什麼,然後嘗試獲得5,"5",...?如果put a 5L5"5"以及想要檢查5F怎麼辦?由於它是一個通用集合(或模板化,或任何你想調用它),它必須檢查併爲某些值類型做一些特殊的比較。如果K爲int,則檢查對象是否通過longshort,float,double,...,然後進行轉換和比較。如果K是float然後檢查物體是否通過...

您明白了。

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

+1

我不明白這一點。當然,等於一個Inter的Long與一個等於Integer的String是有區別的嗎?例如有隱式轉換。 例外可能有助於發現這種情況,是的。 – 2009-01-15 08:57:27

4

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

0

與C++不同,Java語言設計的一部分是針對對象永遠不會隱式地轉換爲其他類型。這是使Java成爲一種小而簡單的語言的一部分。 C++複雜性的一個合理部分來自隱式轉換及其與其他功能的交互。另外,Java在基元和對象之間有一個尖銳而明顯的二分法。這與其他語言的不同之處在於,這種差異作爲優化而隱藏在封面之下。這意味着你不能指望Long和Integer像long和int那樣行事。

可以編寫庫代碼來隱藏這些差異,但實際上可以通過使編程環境不太一致來造成傷害。

6

一般而言,雖然equals()合同中沒有嚴格規定,但對象不應該認爲自己與另一個不完全相同的對象(即使它是子類)相同。考慮對稱性 - 如果a.equals(b)爲真,則b.equals(a)也必須爲真。

讓我們兩個對象,Superfoo,並Sub類,它擴展Superbar。現在考慮在Super中實現equals(),特別是當它被稱爲foo.equals(bar)時。 Foo只知道該欄強制類型爲Object,因此爲了獲得準確的比較,需要檢查它是否爲Super的實例,如果不返回false。這是,所以這部分是好的。它現在比較所有實例字段等(或者實際的比較實現是什麼),並且發現它們是相等的。到現在爲止還挺好。

但是,通過合約,只有在知道bar.equals(foo)也將返回true時才能返回true。由於bar可以是Super的任何子類,因此不清楚equals()方法是否將被覆蓋(如果可能的話)。因此,爲了確保您的實施是正確的,您需要對稱編寫並確保這兩個對象是同一個類。

更根本的,不同類的對象不能真正被認爲是相等的 - 因爲在這種情況下,只有它們中的一個可以被插入到一個HashSet<Sub>,例如。

+0

Long和Integer是不同的類類型,因此使用equals比較它們將始終返回false。 – 2009-01-15 17:42:16

0

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

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 

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

0

另一個答案充分解釋了爲什麼它失敗,但沒有一個解決了如何編寫在這個問題上不太容易出錯的代碼。不得不記住添加類型轉換(無編譯器幫助),帶L等後綴的原語是不可接受的恕我直言。

我強烈建議使用GNU庫的集合庫,當你有原語時(以及其他許多情況下)。例如,有一個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 

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