guava-libraries:Objects.hashCode(Object [])碰撞是否安全?

时间:2011-05-27 23:27:15

标签: java guava hashcode

在查看覆盖hashCode()的不同选项时,我被定向到Google的guava库(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,

  

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

3 个答案:

答案 0 :(得分:6)

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

希望,当您打算构建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();
}

答案 1 :(得分:3)

警告只表示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:

  

如果是两个对象则不需要   是不平等的   等于(java.lang.Object)方法,然后   在每个上调用hashCode方法   两个对象必须产生不同的   整数结果。

与“正常”实现的区别在于此代码的性能会更差。例如,HashMaps将退化为类似于查找性能的列表。

即使有这样的实现:

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

可能(但非常不可能)不同类的哈希值发生冲突。如果两个类的类名的哈希值相同,则会出现这种情况。

使用hashCode(使用hashCode)的同名很多来自不同类型的实例时,这种优化应该是最后的选择。 )内部。整体效果将受到限制:如果您有n种类型,则由于此方案,您最多会发生n次碰撞。其他因素可能会主导性能特征。

答案 2 :(得分:0)

如果要在同一个地图的键集中混合许多不同的具体类型,您仍然可以使用Objects.hashCode()并通过使用每种具体类型的不同值对输出进行编辑来最小化冲突。

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

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

通过使用随机选择的值进行xoring,但是每个具体类都是稳定的,可以消除仅因为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;
  }
}