2013-10-02 27 views
2

這段時間以來,我一直在嗡嗡聲。基本上,每當我想將我自己的一組對象(我稱之爲MyObject)作爲地圖中的鍵存儲時,除非我的課程中保存有相同的確切對象,否則我無法獲得鍵值。儘管我試圖重寫MyObject中的equals方法,但在比較具有相同值的兩個對象時通常返回true,沒有任何更改。地圖檢查密鑰如何?

只給你我的意思示範:


Map<Integer, Integer> map = new HashMap<Integer, Integer>(); 
map.put(2, 3); 
System.out.println(map.get(2))); 

現在,你可能期望的,它搜索地圖的整數對象2,然後打印出3。如果該整數不存在,則它輸出null。到現在爲止還挺好。


Map<String, Integer> map = new HashMap<String, Integer>(); 
map.put(new String("hi"), 3); 
System.out.println(map.get(new String("hi"))); 

這一個作品也如預期。我們只是得到關鍵「嗨」的價值。


Map<MyObject, Integer> map = new HashMap<MyObject, Integer>(); 
map.put(new MyObject(), 3); 
System.out.println(map.get(new MyObject())); 

即使,沒有技術上的「新MyObject來()」和「新MyObject來()」,則返回null之間的區別,無論如何,除非我救了新myObject用作一個實例我的類,並使用該實例作爲get方法的參數。

與我的MyObject相反,如果鍵是字符串或整數,映射很容易獲取鍵值。這些類型是否有特權或者有辦法告訴地圖:「嘿,新創建的對象與那個列表中的類似」?地圖如何比較對象?

+1

地圖將根據實現來檢查密鑰。 HashMap使用哈希碼來完成它。 – porfiriopartida

+0

@porfiriopartida是否有另一個地圖可用於比較兩個相似的對象? –

+0

是的,Map文檔中很少。只需google查詢java.util.Map並查看所有已知實現類部分。 – porfiriopartida

回答

4

就地圖而言,爲了使兩個對象成爲「相同」,它們的hashCode方法必須返回相同的值,並且在將另一個作爲參數傳遞時,方法必須返回true。

默認的Object.hashCodeObject.equals所有對象繼承的方法都與對象標識一起工作,因此即使兩個不同對象的所有字段都相同,它們也是不同的。

所以,當你寫:

map.put(new MyObject(), 3); 
System.out.println(map.get(new MyObject())); 

假設你沒有在MyObject覆蓋hashCodeequals,這些將是與之媲美非等不同的散列碼兩個不同的對象。

如果你希望你的不同的對象是「相同」至於地圖而言(像IntegerString類做),你需要重寫hashCodeequals方法:

class MyObject { 
public int hashCode() { return 42; } 
public boolean equals(Object o) { return o instaceof MyObject; } 
}; 

這將使所有MyObject對象的相同對象相關,就Map而言,您的代碼將打印3

現在,您可能不希望所有MyObjects都完全相同 - 您可能在MyObject中有一些字段,並且只有在字段匹配的情況下,您纔會將它們視爲相同。如果這種情況下,你可能想是這樣的:

class MyObject { 
Object field1; 
Object field2; 
int field3; 
public int hashCode() { return field1.hashCode() + field2.hashCode() + field3; } 
public boolean equals(Object o) { 
    if (!(o instanceof MyObject)) return false; 
    MyObject a = (MyObject)o; 
    return field1.equals(a.field1) && field2.equals(a.field2) && field3 == a.field3; } 
0

地圖是一個接口,它將根據實現檢查密鑰。

java.util.Map

All Known Implementing Classes: 
    AbstractMap, Attributes, AuthProvider, ConcurrentHashMap, ConcurrentSkipListMap, 
    EnumMap, HashMap, Hashtable, IdentityHashMap, LinkedHashMap, PrinterStateReasons, Properties, 
    Provider, RenderingHints, SimpleBindings, TabularDataSupport, TreeMap, UIDefaults, WeakHashMap 

HashMap的是使用此處的實施,它將使用哈希表,當你插入一個對象作爲重點,將獲得它的哈希碼(這是一個整數),並把所需的對象在那個位置,假設這是一個由256個元素組成的數組,如果你的密鑰對象產生了一個5,那麼它將把數值對象存儲到數組中[5]

這裏是get(Object鍵):

HashMap.get(Object)

314  public V get(Object key) { 
315   if (key == null) 
316    return getForNullKey(); 
317   int hash = hash(key.hashCode()); 
318   for (Entry<K,V> e = table[indexFor(hash, table.length)]; 
319    e != null; 
320    e = e.next) { 
321    Object k; 
322    if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 
323     return e.value; 
324   } 
325   return null; 
326  } 

正如你可以看到,有一個空鍵一個特殊插槽,的hashCode屬於對象,所以所有對象都具有特定的整數,之後,如果將獲得具有爲指標所產生的哈希對象,indesOf hash ..

現在的問題是,當2個不同的對象產生相同的hashCode時會發生什麼?

那麼,在get中我們有一個循環,hashCode在對象之間不是唯一的,它將遍歷相同哈希碼的所有對象...所以如果我們有10個對象,其中5個具有相同的哈希碼和所有它們存儲爲密鑰,一旦你嘗試獲得一個重複的哈希碼對象,它將返回5個對象,然後它將使用等於確定哪個是正確的。

TreeMap將做類似的事情,但不使用hashCode,它將使用compareTo的整數。它在內部使用紅黑樹。

就像這兩個,只要你符合接口的契約,實現Map類有很多種方法。

1

要用作Java中Map實例的密鑰,類必須實現一致的hashCode()equals()。在MyObject中沒有實現這些方法的情況下,JVM將使用來自Object的實現,其中兩個實例不相等。

0

的Java HashMap使用hashCodeequals方法來完成其骯髒的工作。不同類型的地圖使用不同的方式,例如TreeMap使用equalscompareTo,因爲它是一個有序地圖。

如果發生類似HashMap的問題,這意味着這些方法的一般合同未得到滿足。從Object類的Java文檔的摘錄:

equals方法實現對非空對象引用的等價關係:

  • 自反性:對於任何非空引用值x,x.equals(x)應該返回true 。
  • 它是對稱的:對於任何非空參考值x和y,當且僅當y.equals(x)返回true時,x.equals(y)應返回true。
  • 它是可傳遞的:對於任何非空引用值x,y和z,如果x.equals(y)返回true,並且y.equals(z)返回true,則x.equals(z)應該返回true。
  • 它是一致的:對於任何非空引用值x和y,如果在對象的等值比較中沒有使用修改的信息,則多個調用x.equals(y)一致地返回true或始終返回false。
  • 對於任何非空參考值x,x.equals(null)應返回false。

注意,這是通常需要覆蓋每當這個方法被覆蓋,以便維持對hashCode方法,其中指出,等於對象必須具有相等的散列碼的一般合同hashCode方法。

hashCode常規協定是:

  • 如果兩個對象是根據equals(Object)方法相等,則調用每個兩個對象的hashCode方法必須產生相同的整數結果。
  • 根據equals(java.lang.Object)方法,如果兩個對象不相等,則不要求對兩個對象中的每個對象調用hashCode方法必須產生不同的整數結果。但是,程序員應該意識到,爲不相等的對象生成不同的整數結果可能會提高散列表的性能。

記住,不提供始終如一的貫徹hashCode方法,如果平等是實現什麼情況是,兩個因素被認爲是平等的,但它的散列碼是不同的,因此,奇怪的事情(比如在你的例子)發生。

現在,如果HashMap不像預期的那樣對您的自定義對象起作用,那麼99.99%您不尊重這些合同條目中的一個或多個。爲具有合成的對象提供一致的哈希代碼並不那麼簡單,但有許多簡單的解決方案足夠好。

3

對於任何哈希啓用數據結構(如HashMapHashSet)正常工作的元件或鍵必須倍率hashCode()除了equals()方法。原因在於哈希碼用於標識要在其中插入元素或鍵(在插入過程中)或搜索(在查找過程中使用equals())的存儲區。

如果不重寫hashCode(),從Object#hashCode()默認實現使用將即使你認爲等同的對象返回不同的值(equals()方法返回真正)。

這就是爲什麼你的

may.get(myObject) 

調用盡管myObject已經存在的失敗。因爲,哈希碼不匹配HashMap決不會在右邊的桶中查找關鍵字。因此,您的equals()永遠不會在這裏被調用。

0

與我的MyObject相反,如果 鍵是字符串或整數,映射很容易獲取鍵值。是那些類型只是特權或 有一種方法來告訴地圖:「嘿,新創建的對象是類似的 與該列表中的那個」?地圖如何比較對象?

在JAVA中查看TreeMap。 TreeMap允許我們在創建時指定一個可選的Comparator對象。這些鍵應該與指定的比較器兼容。該比較器決定鍵需要排序的順序。 TreeMap比hashmap慢。

0

Map實現在對象中使用hashCode()方法來確定在調用get時在其內部數據結構中查找的密鑰。在您的具體的例子,讓我們假設你的MyObject類有一個id屬性:

public class MyObject { 
    private int id; 

    public MyObject(int id) { 
     this.id = id; 
    } 

    public int getId() { 
     return id; 
    } 
} 

假設你想要的id財產地圖查找時鍵使用 - 對象的實例無關 - 你」 ð覆蓋hashCode方法在你的類來做到這一點:

public class MyObject { 
    private int id; 

    public MyObject(int id) { 
     this.id = id; 
    } 

    public int getId() { 
     return id; 
    } 

    /** 
    * Uses the Jakarta commons-lang HashCodeBuilder class to generate the hash code. 
    * 
    * @see http://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/builder 
    * /HashCodeBuilder.html 
    */ 


    public int hashCode() { 
     return new HashCodeBuilder(1, 5) 
       .append(id) 
       .toHashCode(); 
    } 

    public boolean equals(Object other) { 
     if (other == null) 
      return false; 

     if (other == this) 
      return true; 

     return (other.id == this.id); 
    } 
} 

documentation爲HashCodeBuilder解釋了什麼是數字1和5,作爲參數傳遞給HashCodeBuilder過去了,不要 - 我拿起他們隨機:該文件說,他們必須是唯一的,隨機,奇數。