簡體   English   中英

Java HashMap containsKey為現有對象返回false

[英]Java HashMap containsKey returns false for existing object

我有一個用於存儲對象的HashMap:

    private Map<T, U> fields = Collections.synchronizedMap(new HashMap<T, U>());

但是,當試圖檢查密鑰的存在時, containsKey方法返回false
equalshashCode方法,但找不到密鑰。
調試一段代碼時:

    return fields.containsKey(bean) && fields.get(bean).isChecked();

我有:

   bean.hashCode() = 1979946475 
   fields.keySet().iterator().next().hashCode() = 1979946475    
   bean.equals(fields.keySet().iterator().next())= true 
   fields.keySet().iterator().next().equals(bean) = true

fields.containsKey(bean) = false

什么可能導致這種奇怪的行為?

public class Address extends DtoImpl<Long, Long> implements Serializable{

   <fields>
   <getters and setters>

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + StringUtils.trimToEmpty(street).hashCode();
    result = prime * result + StringUtils.trimToEmpty(town).hashCode();
    result = prime * result + StringUtils.trimToEmpty(code).hashCode();
    result = prime * result + ((country == null) ? 0 : country.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Address other = (Address) obj;
    if (!StringUtils.trimToEmpty(street).equals(StringUtils.trimToEmpty(other.getStreet())))
        return false;
    if (!StringUtils.trimToEmpty(town).equals(StringUtils.trimToEmpty(other.getTown())))
        return false;
    if (!StringUtils.trimToEmpty(code).equals(StringUtils.trimToEmpty(other.getCode())))
        return false;
    if (country == null) {
        if (other.country != null)
            return false;
    } else if (!country.equals(other.country))
        return false;
    return true;
}


}

將密鑰插入地圖后,您不得修改密鑰。

編輯:我在Map中找到了javadoc的摘錄:

注意:如果將可變對象用作映射鍵,則必須非常小心。 如果在對象是地圖中的鍵的情況下以影響等於比較的方式更改對象的值,則不指定映射的行為。

使用簡單包裝類的示例:

public static class MyWrapper {

  private int i;

  public MyWrapper(int i) {
    this.i = i;
  }

  public void setI(int i) {
    this.i = i;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    return i == ((MyWrapper) o).i;
  }

  @Override
  public int hashCode() {
    return i;
  }
}

和測試:

public static void main(String[] args) throws Exception {
  Map<MyWrapper, String> map = new HashMap<MyWrapper, String>();
  MyWrapper wrapper = new MyWrapper(1);
  map.put(wrapper, "hello");
  System.out.println(map.containsKey(wrapper));
  wrapper.setI(2);
  System.out.println(map.containsKey(wrapper));
}

輸出:

true
false

注意:如果你不重寫hashcode(),那么你只會得到真實

正如Arnaud Denoyelle所指出的那樣,修改密鑰會產生這種效果。 原因containsKey關心哈希映射中的密鑰桶,而迭代器則不然。 如果地圖中的第一個鍵 - 忽略存儲桶 - 恰好是您想要的那個,那么您可以獲得您所看到的行為。 如果地圖中只有一個條目,這當然是有保證的。

想象一個簡單的雙桶地圖:

[0: empty]  [1: yourKeyValue]

迭代器是這樣的:

  • 迭代桶0中的所有元素:沒有
  • 迭代桶1中的所有元素:只是一個yourKeyValue

但是, containsKey方法如下所示:

  • keyToFind有一個hashCode() == 0 ,所以讓我看看桶0( 只有那里)。 哦,它是空的 - 返回false.

事實上,即使密鑰停留在同一個桶中,您仍然會遇到此問題! 如果查看HashMap的實現,您將看到每個鍵值對與鍵的哈希碼一起存儲。 當地圖想要針對傳入的密鑰檢查存儲的密鑰時,它使用此hashCode和密鑰的equals

((k = e.key) == key || (key != null && key.equals(k))))

這是一個很好的優化,因為這意味着碰巧碰到同一個桶的具有不同hashCodes的密鑰將被視為非等價非常便宜(只是一個int比較)。 但這也意味着更改密鑰 - 不會更改存儲的e.key字段 - 將破壞地圖。

調試java源代碼我意識到方法containsKey檢查搜索鍵上的兩個東西對着密鑰集中的每個元素: hashCodeequals ; 它按順序執行。

這意味着如果obj1.hashCode() != obj2.hashCode() ,它返回false(不評估obj1.equals(obj2)。但是,如果obj1.hashCode() == obj2.hashCode() ,則返回obj1.equals(obj2)

您必須確保兩種方法 - 可能必須覆蓋它們 - 對於您定義的標准,評估為true。

以下是您的問題的SSCCE 它就像一個魅力,它不可能是其他的,因為你的hashCodeequals方法似乎是由IDE自動生成的,它們看起來很好。

所以,關鍵字是when debugging 調試本身可能會損害您的數據。 例如,在調試窗口的某處,您可以設置更改fields對象或bean對象的表達式。 之后,您的其他表達式將給您意想不到的結果。

嘗試在方法中添加所有這些檢查,從中獲得return語句並打印出結果。

import org.apache.commons.lang.StringUtils;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class Q21600344 {

    public static void main(String[] args) {
        MapClass<Address, Checkable> mapClass = new MapClass<>();
        mapClass.put(new Address("a", "b", "c", "d"), new Checkable() {
            @Override
            public boolean isChecked() {
                return true;
            }
        });

        System.out.println(mapClass.isChecked(new Address("a", "b", "c", "d")));
    }

}

interface Checkable {
    boolean isChecked();
}

class MapClass<T, U extends Checkable> {
    private Map<T, U> fields = Collections.synchronizedMap(new HashMap<T, U>());

    public boolean isChecked(T bean) {
        return fields.containsKey(bean) && fields.get(bean).isChecked();
    }

    public void put(T t, U u) {
        fields.put(t, u);
    }
}

class Address implements Serializable {

    private String street;
    private String town;
    private String code;
    private String country;

    Address(String street, String town, String code, String country) {
        this.street = street;
        this.town = town;
        this.code = code;
        this.country = country;
    }

    String getStreet() {
        return street;
    }

    String getTown() {
        return town;
    }

    String getCode() {
        return code;
    }

    String getCountry() {
        return country;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + StringUtils.trimToEmpty(street).hashCode();
        result = prime * result + StringUtils.trimToEmpty(town).hashCode();
        result = prime * result + StringUtils.trimToEmpty(code).hashCode();
        result = prime * result + ((country == null) ? 0 : country.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Address other = (Address) obj;
        if (!StringUtils.trimToEmpty(street).equals(StringUtils.trimToEmpty(other.getStreet())))
            return false;
        if (!StringUtils.trimToEmpty(town).equals(StringUtils.trimToEmpty(other.getTown())))
            return false;
        if (!StringUtils.trimToEmpty(code).equals(StringUtils.trimToEmpty(other.getCode())))
            return false;
        if (country == null) {
            if (other.country != null)
                return false;
        } else if (!country.equals(other.country))
            return false;
        return true;
    }


}

暫無
暫無

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

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