guava库:Objects.hashCode(Object[])冲突安全



在研究覆盖hashCode()的不同选项时,我被引导到谷歌的番石榴库(javadoc)中的Objects.hashCode(Object[])。javadoc声明它委托给Arrays.hashCode(Object[])。在许多不同的对象类型中使用此方法是否安全?这不是很容易发生哈希冲突吗?或者这不太可能仅仅因为容器通常只包含一种类型的对象?

作为一个简单的例子,考虑以下类,

public class Student {
    private final String name;
    public Student(String name) {
        this.name = name;
    }
    @Override
    public int hashCode() {
        return Objects.hashCode(name);
    }
}
public class Teacher {
    private final String name;
    public Teacher(String name) {
        this.name = name;
    }
    @Override
    public int hashCode() {
        return Objects.hashCode(name);
    }
}
public class HashCodeDriver {
    public static void main(String[] args) {
        final String name = "moe";
        Student s = new Student(name);
        Teacher t = new Teacher(name);
        long studentHash = s.hashCode();
        long teacherHash = t.hashCode();
        System.out.println("studentHash=" + studentHash + " teacherHash=" + teacherHash);
        if(studentHash == teacherHash) {
            System.out.println("hash codes match");
        }
        else {
            System.out.println("hash codes don't match");
        }
    }
}

输出:

studentHash=108322 teacherHash=108322
hash codes match

对象是两种不同的类型,但生成的哈希代码相同。这不是个问题吗?我应该将类作为第一个参数传入以防止这种冲突吗?例如,

public class Student {
    private final String name;
    public Student(String name) {
        this.name = name;
    }
    @Override
    public int hashCode() {
        return Objects.hashCode(Student.class, name);
    }
}
public class Teacher {
    private final String name;
    public Teacher(String name) {
        this.name = name;
    }
    @Override
    public int hashCode() {
        return Objects.hashCode(Teacher.class, name);
    }
}

这就是javadoc警告只向该方法提供单个对象的原因吗?来自javadoc,

警告:当提供单个对象时,返回的哈希代码不等于该对象的哈希代码。

当两个不同类型的两个不同对象具有相同的哈希代码时,这不是问题。

希望,当你要构建你的HashMap时,你不会把学生和老师混合在一起作为地图的关键。即使在你想做HashMap<Object, Object>的情况下,你也可以,因为

assertFalse( new Teacher( "John Smith" ).equals( new Student( "John Smith" ) );

这就是为什么覆盖hashCodeequals很重要。

委托给Arrays.hashCode(Object[])的唯一缺点可能是,从性能的角度来看,有时它可能过于昂贵。

例如,在您的情况下,对于教师或学生来说,这将是一个更好的哈希方法。

@Override
public int hashCode() {
    return name.hashCode();
}

警告仅表示x.hashCode() != Objects.hashCode(x)为真。(好吧,大多数时候都是这样。它们仍然可能在某些值上发生冲突。实际上,对于大多数对象来说,这并不相等。)

一个有效的hashCode/equals实现是:

public class Teacher {
    private final String name;
    public Teacher(String name) {
        this.name = name;
    }
    @Override public equals(Object obj){
        if(obj == this) return true;
        if(!(obj instanceof Teacher)) return false;
        return Objects.equal(name, ((Teacher) obj).getName());
    }
    @Override public hashCode(){
        return 0;
    }
}

尽管所有哈希值都冲突,但这是有效的。来自hashCode()javadoc:

如果两个对象根据equals(java.lang.Object)方法,然后在这两个对象必须产生不同的整数结果。

与"正常"实现的不同之处在于,此代码的性能会差得多。例如,HashMaps将退化为类似列表的查找性能。

即使有这种实现:

@Override
public int hashCode() {
    return Objects.hashCode(Teacher.class, name);
}

不同类的散列值可能发生冲突(但可能性很小)。如果两个类的类名哈希值相同,就会出现这种情况。

在内部使用hashCode()的集合中存在许多来自不同类型且具有相同名称的实例时,这种优化应该是最后的手段。总体效果是有限的:如果你有n种类型,那么由于这种情况,你最多会有n次碰撞。其他因素可能主导性能特征。

如果在同一映射的键集中混合了许多不同的具体类型,则仍然可以使用Objects.hashCode(),并通过对每个具体类型的输出使用不同的值来最小化冲突。

class Class1 {
  public int hashCode() {
    return Object.hashCode(...) ^ 0x12b7eff8;
  }
}
class Class2 {
  public int hashCode() {
    return Object.hashCode(...) ^ 0xe800792b;
  }
}

通过使用一个随机选择但每个具体类都稳定的值进行异或运算,可以消除仅因为Object.hashCode的参数等效而可能发生的冲突的可能性。

警告:当提供单个对象时,返回的哈希代码不等于该对象的哈希代码。

这就是javadoc警告只向该方法提供单个对象的原因吗?来自javadoc,

没有。此警告不是关于具有相同成员的不同具体类的实例之间发生冲突的可能性。相反,由于假设单个值的哈希与singleValue.hashCode()的哈希相同,它会警告哈希代码匹配中的假阴性。

例如,看看下面在错误的快速通道代码中做出的假设,该代码试图通过使用缓存的哈希代码来避免相等性检查:

class Name {
  int cachedHashCode;
  ...
}
class Person {
  int cachedHashCode;  // 0 if not computed
  private final Name name;
  public boolean hasName(Name n) {
    return ((cachedHashCode != 0 && n.cachedHashCode != 0) 
            && cachedHashCode == n.cachedHashCode)
        || n.equals(name);
  }
  public int hashCode() {
    if (cachedHashCode == 0) { cachedHashCode = Object.hashCode(name); }
    return cachedHashCode;
  }
}

最新更新