转载

hashCode与==与equals

hashCode

public int hashCode()

三条规则

  • 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 hash tables.

大概意思:

  • 在一个应用程序的执行同一个程序时,任意时刻调用同一对象的hashCode,返回值必须相同,除非他的equals方法被重写。
  • 如果两个对象就equals方法相等,那么他们的hashCode值也相等。
  • 如果两个对象就equals方法不相等,他们的hashCode值也可以不相等。但是,程序员应该意识到,不同的对象返回不同的hashCode会提高哈希表的效率,减少碰撞率。

什么情况下要重写?怎么重写?往下看。

==

对于基本数据类型,比较值

对于引用数据类型,比较地址

equals

public boolean equals(Object obj)

特性:自反性、对称性、传递性、一致性、非空值与空值比较返回false

具体实现要看该对象是怎么实现的

Object类中 equals 的定义

The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y , this method returns true if and only if x and y refer to the same object ( x == y has the value true ).

看源码就能理解他的意思,也就是直接比较两个对象的内存地址。

public boolean equals(Object obj) {
    return (this == obj);
}

当然我们也可以通过重写 equals 方法来进行其他对象的比较。

比如Integer,比较值

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

比如String,比较内容

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

比如自定义的User

public class User {
    private String id;
    private String name;
}

如果你不重写,他还是继承Object的equals方法实现,也就是比较两个对象的内存地址。

@Override
public boolean equals(Object obj) {
    return super.equals(obj);
}

返回结果,既然是两个不同的对象,那么其内存地址自然不相等。

hashCode与==与equals

重写后(比较各字段值)

@Override
public boolean equals(Object obj) {
    if (obj == this) return true; // 判断obj对象是否是User的引用
    if (obj instanceof User) { // 检查obj是否为正确的类型
        User user = ((User) obj);
        // 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配
        return user.id.equals(id) && user.name.equals(name);
    }
    return false;
}

返回结果

hashCode与==与equals

为什么重写equals方法后一定要重写hashCode?

为了维护hashcode的三条原则,为了集合类的使用。

回忆一下hashcode原则的第二条:

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.

equals结果相等,两者的hashCode也应该相同。

且我们知道, 集合类判断对象是否相等的两个条件:1.equals结果返回true;2.两者返回的hashCode值相同

因此重写equals的同时,也应该重写hashCode方法

比较未重写hashCode的User对象的hashCode

hashCode与==与equals

重写后

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

返回结果

hashCode与==与equals

equals的正确重写姿势

  1. 使用 == 操作符检查参数是否为这个对象的 引用
  2. 使用 instanceof 操作符检查参数是否为正确的类型
  3. 对于类中的 关键属性 ,检查参数传入对象的属性是否与之相匹配;

    1. 手动比较: user.id.equals(id) && user.name.equals(name)
    2. JDK7及以上,调用Objcects.equals比较: Objects.equals(id, user.id) && Objects.equals(name, user.name)

      原理相同

      public static boolean equals(Object a, Object b) {
          return (a == b) || (a != null && a.equals(b));
      }
  4. 编写完 equals 方法后,问自己它是否满足 特性
  5. 重写 equals 时 总是要重写hashCode
  6. 不要将 equals 方法参数中的 Object 对象替换为其他的类型,在重写时不要忘掉 @Override 注解。

hashCode的正确重写姿势

你可以自定义,也可以选择其他有效的方式。

但是自定义的不一定高效,且要保证: 如果你在重写时用到了对象类型,那么你也要同时保证被引用对象也已经重写了hashCode方法 ,否则每次得到的他的HashCode不一定相同,从而影响到当前对象返回的hashCode值。

那么如何高效的重写HashCode呢?

摘自

  • 经典方式

    @Override
    public int hashCode() {
        // Classic Way
        int result = 17;
        result = 31 * result + (id == null ? 0 : id.hashCode());
        result = 31 * result + (name == null ? 0 : name.hashCode());
        return result;
    }
  • JDK7

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

    其实他的原理也是一样的

    @Override
    public static int hashCode(Object a[]) {
        if (a == null)
            return 0;
    
        int result = 1;
    
        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());
    
        return result;
    }
  • Apache Commons Lang

    这种没尝试过

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 37)
             .append(id)
             .append(name)
             .toHashCode();
    }

以下摘录自《Effective Java》

重写hashCode的通用方法

  1. 初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;
  2. 选取equals方法中用于比较的所有域,然后针对每个域的属性进行计算:

    1. 如果是boolean值,则计算f ? 1:0
    2. 如果是bytecharshortint,则计算(int)f
    3. 如果是long值,则计算(int)(f ^ (f >>> 32))
    4. 如果是float值,则计算Float.floatToIntBits(f)
    5. 如果是double值,则计算Double.doubleToLongBits(f),然后返回的结果是long,再用规则(3)去处理long,得到int
    6. 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0
    7. 如果是数组,那么需要为每个元素当做单独的域来处理。如果你使用的是1.5及以上版本的JDK,那么没必要自己去重新遍历一遍数组,java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上。
原文  https://segmentfault.com/a/1190000022083619
正文到此结束
Loading...