[英]Why is Long.valueOf(0).equals(Integer.valueOf(0)) false?
這個問題是由奇怪的HashMap.put()行為引起的
我想我明白為什么Map<K,V>.put
采用K
但Map<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
foo
和Sub
類的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
則檢查傳遞的對象是long
, short
, float
, double
,...,然后轉換和比較。 如果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.