java中的哈希代码实现

时间:2013-11-17 09:29:07

标签: java

package MavenWeb.MavenWeb;

import java.util.HashMap;
import java.util.Map;

public class StringEquality {

    public static void main(String[] args) {

        Person p1 = new Person("Naveen", 22, 1000);
        Person p2 = new Person("Naveen", 21, 2000);
        Person p3 = new Person("Naveen", 23, 3000);

        if(p1.equals(p2)){
            System.out.println("P1 and p2 :" + p1.equals(p2));
        } else{
            System.out.println("P1 and p2 :" + p1.equals(p2));
        }

        if(p1.equals(p3)){
            System.out.println("P1 and p3 :" + p1.equals(p3));
        }


        Map<Person, Object> map = new HashMap<Person, Object>();
        map.put(p1, p1);
        map.put(p2, p2);


        System.out.println(map.get(new Person("Naveen", 21, 2000)));


    }

}

...

class Person{

    public Person(String name, int id, float salary){
        this.name = name;
        this.id = id;
        this.salary = salary;
    }
    public Float getSalary() {
        return salary;
    }
    public void setSalary(Float salary) {
        this.salary = salary;
    }
    String name;
    Float salary;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    int id;


    @Override
    public boolean equals(Object obj) {
        if(obj == null){
            return false;
        }

        if(this == obj){
            return true;
        }

        if(obj instanceof Person){

            Person person = (Person)obj;

            if((person.getName().equals(name)) && person.getId() == id
                    && person.getSalary() == salary){
                return true;
            }
        }
        return false;
    }


    @Override
    public int hashCode() {
        int hashCode = 1;
        hashCode = 31 * hashCode + name.hashCode();
        hashCode = 31 * hashCode + id;
        hashCode = 31 * hashCode + salary.intValue();
        return hashCode;
    }

    @Override
    public String toString() {
        return "Name :" + name + ", id : " + id;
    }
}

我写了以下程序。关于哈希码实现有点困惑,

  1. 为什么我们在实施中使用31
  2. 根据equals方法逻辑,该程序应返回p2(Naveen,21)实例,但它返回null。为什么呢?

4 个答案:

答案 0 :(得分:6)

您从地图中获取null,因为薪水存储为Float(而不是float),并且您将Float个实例与==进行比较而不是将它们与equals()进行比较。

因此Person的equals()方法检查两个Person实例中完全相同的Float实例,而不是检查它们的工资值是否相等。您应该使用float(或更好:double),除非薪水可以为空(您还应该考虑使用BigDecimal来处理确切的金额)。但在这种情况下,您必须在Person.equals()中检查空值并使用equals()来比较工资。在Java 7或更高版本中,最简单的方法是使用Objects.equals()将可空对象进行比较:

@Override
public boolean equals(Object obj) {
    if(obj == null){
        return false;
    }

    if(this == obj){
        return true;
    }

    if(obj instanceof Person){

        Person person = (Person)obj;

        return Objects.equals(name, person.name)
               && id == person.id
               && Objects.equals(salary, person.salary);
    }
    return false;
}


@Override
public int hashCode() {
    return Objects.hash(name, id, salary);
}

答案 1 :(得分:3)

您使用了数字31,因此由您决定是什么意思。

数字31用于字符串,因为它是奇数素数(产生更多随机哈希码)并且大于字母表中的字母数,因此不同的字母不太可能为不同的文本产生相同的哈希码。使用31,您可以拥有全部5个字符如果使用A-Z或a-z,则字符串是唯一的。

然而,对于更长的字符串,我发现57对我来说效果更好,对于非字符串,其他更大的素数如10191可能更好。我喜欢10191,101,1019和10191是素数。

答案 2 :(得分:1)

问题是你的equals实现是错误的。

考虑一下:

Person p4 = new Person("Naveen", 21, 2000);
System.out.println(p2.equals(p4));

它打印为false,或者应该打印为true。

map.get(Object key)需要使用equals方法检查密钥是否已存储在地图中。

  

更正式地说,如果此地图包含从键k到值的映射   v这样(key == null?k == null:key.equals(k))

你的equals方法有什么问题?

在你的equals方法中,你应该替换

person.getSalary() == salary



    1. person.getSalary().equals(salary)
    2. person.getSalary().floatValue() == salary.floatValue()

由于getSalary()会返回Float个对象,因此==会检查其引用而不是其值。

不需要Float包装器类,您应该使用float salary;double salary;(如果您想要更高的精度)。

您可以使用Eclipse生成的equals实现:

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Person other = (Person) obj;
    if (id != other.id)
        return false;
    if (name == null) {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    if (salary == null) {
        if (other.salary != null)
            return false;
    } else if (!salary.equals(other.salary))
        return false;
    return true;
}


System.out.println(map.get(new Person("Naveen", 21, 2000)));

打印

Name :Naveen, id : 21

答案 3 :(得分:0)

你可以读到这个:

http://www.xyzws.com/javafaq/why-always-override-hashcode-if-overriding-equals/20

为什么总是覆盖hashcode(),如果重写equals()?

在Java中,每个对象都可以访问equals()方法,因为它是从Object类继承的。但是,此默认实现只是简单地比较对象的内存地址。您可以覆盖java.lang.Object中定义的equals()方法的默认实现。如果重写equals(),则必须覆盖hashCode()。否则,将违反Object.hashCode的常规合同,当您的类与所有基于散列的集合一起使用时,可能会产生意外的后果。

这是从java.lang.Object专业化中复制的契约:

public int hashCode()

Returns a hash code value for the object. This method is supported for the benefit of hashtables such as those provided by java.util.Hashtable.

The general contract of hashCode is:

    Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
    If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
    It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hashtables.

As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)

equals()方法的默认实现检查两个对象是否具有相同的标识。类似地,hashCode()方法的默认实现基于对象的标识返回一个整数,而不是基于对象的实例(和类)变量的值。无论其实例变量(数据字段)的值发生多少次变化,默认hashCode实现计算的哈希码在对象的生命周期内都不会发生变化。

考虑下面的代码,我们重写了equals()方法,根据实例变量的值检查两个对象是否相等。两个对象可以存储在不同的存储器地址中,但基于它们的实例变量可能仍然相等。

public class CustomerID {
  private long crmID;
  private int nameSpace;

  public CustomerID(long crmID, int nameSpace) {
    super();
    this.crmID = crmID;
    this.nameSpace = nameSpace;
  }

  public boolean equals(Object obj) {
    //null instanceof Object will always return false
    if (!(obj instanceof CustomerID))
      return false;
    if (obj == this)
      return true;
    return  this.crmID == ((CustomerID) obj).crmID &&
            this.nameSpace == ((CustomerID) obj).nameSpace;
  }

  public static void main(String[] args) {
    Map m = new HashMap();
    m.put(new CustomerID(2345891234L,0),"Jeff Smith");
    System.out.println(m.get(new CustomerID(2345891234L,0)));
  }

}

编译并运行上面的代码,输出结果为

null

有什么问题?根据类的equals方法,CustomerID的两个实例在逻辑上是相等的。由于未覆盖hashCode()方法,因此这两个实例的标识与默认的hashCode实现不同。因此,Object.hashCode返回两个看似随机的数字而不是两个相等的数字。此类行为违反了hashCode合同中定义的“Equal objects必须具有相同的哈希码”规则。

让我们提供一个简单的hashCode()方法来解决这个问题:

public class CustomerID {
  private long crmID;
  private int nameSpace;

  public CustomerID(long crmID, int nameSpace) {
    super();
    this.crmID = crmID;
    this.nameSpace = nameSpace;
  }

  public boolean equals(Object obj) {
    //null instanceof Object will always return false
    if (!(obj instanceof CustomerID))
      return false;
    if (obj == this)
      return true;
    return  this.crmID == ((CustomerID) obj).crmID &&
            this.nameSpace == ((CustomerID) obj).nameSpace;
  }

  public int hashCode() {
    int result = 0;
    result = (int)(crmID/12) + nameSpace;
    return result;
  }

  public static void main(String[] args) {
    Map m = new HashMap();
    m.put(new CustomerID(2345891234L,0),"Jeff Smith");
    System.out.println(m.get(new CustomerID(2345891234L,0)));
  }

}

编译并运行上面的代码,输出结果为

Jeff Smith

类实例的哈希码分布应该是随机的。这正是hashCode合约的第三个规定的含义。编写正确的hashCode方法很简单,但编写有效的hashCode方法极为困难。

例如,从如何避免陷阱和正确覆盖java.lang.Object中的方法:如果您不确定如何实现hashCode(),只需在实现中返回0。因此,所有自定义对象都将返回相同的哈希码。是的,它将您的对象的哈希表转换为一个(可能)长链表,但您已正确实现了hashCode()!

public int hashCode(){
  return 0;
}

这是合法的,因为它确保相等的对象具有相同的哈希码,但它也表明每个对象具有相同的哈希码。因此,每个对象都将被散列到同一个桶中,并且哈希表会退化为链接列表。当需要处理大量对象时,性能会变差。如何实现一个好的哈希函数是一个很大的主题,我们不会在这里介绍