HashMap使用不正确的equals和HashCode实现

4
根据我所了解的,要将一个对象用作hashMap的键,它必须提供正确的重写和实现equals和hashCode方法。HashMap的get(Key k)方法调用key对象的hashCode方法,并将返回的哈希值应用于其自身的静态哈希函数,以查找存储键和值的桶位置(支持数组),以嵌套类Entry(Map.Entry)的形式存储。HashMap内部的哈希方法可以防止质量较差的哈希函数。
为了测试这些协议,我编写了一个bean类,其中包含不正确但合法的equals和hashCode方法实现。
该类:
public class HashVO {

    private String studentName;
    private int age;
    private boolean isAdult;

    public HashVO(String studentName, int age, boolean isAdult) {
        super();
        this.studentName = studentName;
        this.age = age;
        this.isAdult = isAdult;
    }
    public String getStudentName() {
        return studentName;
    }
    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public boolean isAdult() {
        return isAdult;
    }
    public void setAdult(boolean isAdult) {
        this.isAdult = isAdult;
    }
    @Override
    public String toString() {
        return studentName + " : " + age + " : " + isAdult;
    }
    @Override
    public boolean equals(Object obj) {
        return false;
    }
    @Override
    public int hashCode() {
        return 31;
    }

}

在这种情况下,HashMap的哈希方法,
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

这个方法每次都应该返回相同的值,因为哈希码总是返回31。因此,如果将HashVO类对象用作hashMap的键,get方法将不起作用,因为它应该去同一个桶中检索对象,而equals方法总是返回false,所以它无法找到与键对象匹配的对象
但是当我使用这个方法时,
public static void main(String[] args) {
        HashMap<HashVO, String> voMap = new HashMap<HashVO, String>();
        HashVO vo = new HashVO("Item1", 25, true);
        HashVO vo1 = new HashVO("Item2", 12, false);
        HashVO vo2 = new HashVO("Item3", 1, false);
        voMap.put(vo, "Item");
        voMap.put(vo1, "Item1");
        voMap.put(vo2, "Item2");
        System.out.println(voMap.get(vo));
        System.out.println(voMap.get(vo1));
        System.out.println(voMap.get(vo2));
    }

输出结果是正确的,且已经显示。
Item
Item1
Item2

我想了解为什么即使Equals和HashCode方法的实现不正确,也会出现正确的输出结果。
2个回答

7

HashMap有一个小技巧,在使用equals之前比较对象引用。由于您在添加元素和检索元素时使用相同的对象引用,HashMap将正确返回它们。

请参见Java 7源代码这里(Java 8对HashMap进行了相当大的改进,但执行了类似的操作)。

final Entry<K,V> getEntry(Object key) {
    if (size == 0) {
        return null;
    }

    int hash = (key == null) ? 0 : hash(key);
    for (Entry<K,V> e = table[indexFor(hash, table.length)];
         e != null;
         e = e.next) {
        Object k;
        // HERE. Uses == with the key
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k)))) 
            return e;
    }
    return null;
}

请注意,这不是文档的一部分,所以不要依赖它。

我认为你是正确的。我使用Java 8来运行程序。在Java 8中,HashMap在getNode方法(第566行)中也有类似的实现。 - dripto

1
HashMap的工作原理如下:
1)计算键的hashCode()值,以确定(Key,Value)将保存在表格单元格的索引中;
2)在HashMap中,通过equals()方法或引用比较来比较键。
因此,在您的情况下,所有(K,V)对都将存储在HashMap表的一个单元格中,作为LinkedList。并且您可以从Map中获取它们,因为键的引用将相等。

应该使用k.equals(key)而不是key.equals(k),这样映射中的键就决定了它们映射到的内容,而不是提供给get方法的键。例如,假设我有一个代表所有长度为8的字符串的数据结构。我将其定义为FixedSizeStringOfLength8类。我将hashCode()和equals()定义为与String相同。但是,下面的代码将使x等于null。Map map = new HashMap<>(); map.put(new FixedSizeStringOfLength8("12345678"), "Blah"); String x = map.get("12345678"); - Prof Mo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接