hashCode和equals

最近看集合框架的源码,遇到一个技术细节,就是
Java 超类java.lang.Object中定义的两个非常重要方法

  • public boolean equals(Object obj)
  • public int hashCode()

参考了一个文档Equals and Hash Code将这个技术点做个整理

equals

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

Object类中默认的实现方式是 :return (this == obj);
也就是两个引用指向同一个对象才会返回true。指向同一个对象,必然是等价的,但是我们在实际使用中往往是要判断两个对象是否等价,而不是判断是不是多个引用指向同一个对象。所以需要重写equals方法
JDK API中指出equals要满足以下规则:

自反性:对于任何参考值 x,x.equals(x) 应该返回 true
对称性:对于任何参考值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true
传递性:对于任何参考值 x、y 和 z,如果 x.equals(y) 返回 true 并且 y.equals(z) 返回 true,那么 x.equals(z) 应该返回 true。
一致性:对于任何引用值 x 和 y,x.equals(y) 的多次调用始终返回 true 或始终返回 false,前提是没有修改对象的 equals 比较中使用的信息。

equals编写示例

class Test {
    private int num;
    private String data;
    @Override
    public boolean equals(Object obj) {
        // 引用相同,指向同一个对象,必然相等,不需要进一步比较,直接返回true
        if (this == obj) {
            return true;
        }

        /*
         * this能调用equals方法,必然不为null,如果obj为null,两者当然不等价
         * 比较两个对象的运行时类型,不是同一个类,当然也不等价
         * PS: 这里也排除了运行时类型为父类子类进行比较的情况,还是返回false
         */
        if ((obj == null) || (obj.getClass() != this.getClass())) {
            return false;
        }

        // obj和this同运行时类型相同 且 不是null,强转之后比较属性。
        Test test = (Test) obj;
        // 比较两个属性都相等才返回true,注意处理 data == null 的情况 
        return  num == test.num && (data == test.data || (data != null && data.equals(test.data)));
        }
	@Override
	public int hashCode() {
		...
	}
}
  1. 避免用第二种错误写法代替第一种
if((obj == null) || (obj.getClass() != this.getClass())) return false; // prefer
if(!(obj instanceof Test)) return false; // avoid

例如:假设Dog扩展了Aminal类。
dog instanceof Animal 得到true
animal instanceof Dog 得到false
这就会导致
animal.equls(dog) 返回true
dog.equals(animal) 返回false

  1. 按照第一种方法实现,那么equals只能比较同一个类的对象,不同类对象永远是false。但这并不是强制要求的。一般我们也很少需要在不同的类之间使用equals。
    PS: 父类引用可以指向子类对象,如果animal引用指向dog对象,animal.equls(dog) 实际上比的还是两个dog对象

  2. 在具体比较对象的字段的时候

    • 对于基本值类型的字段,直接用 == 来比较(注意浮点数的比较,这是一个坑)
    • 对于引用类型的字段,你可以调用他们的equals,当然,你也需要处理字段为null 的情况。
    • 对于浮点数的比较,我在看Arrays.binarySearch的源代码时,发现了如下对于浮点数的比较的技巧
if ( Double.doubleToLongBits(d1) == Double.doubleToLongBits(d2) ) //d1 和 d2 是double类型

if(  Float.floatToIntBits(f1) == Float.floatToIntBits(f2)  )      //f1 和 f2 是d2是float类型
  1. 并不总是要将对象的所有字段来作为equals 的评判依据,那取决于你的业务要求。比如两台相同的电视机是可以卖不同的价格,而我们比较这两台电视是否一样时可以不关心价格的。
  2. equals 方法的参数类型是Object

hashCode

public native int hashCode();

这个方法返回对象的散列码,返回值是int类型的散列码
对象的散列码是为了更好的支持基于哈希机制的Java集合类,例如 Hashtable, HashMap, HashSet 等

对hashCode方法,有以下约定:

  1. 在某个运行时期间,只要对象的(字段的)变化不会影响equals方法的决策结果,那么,在这个期间,无论调用多少次hashCode,都必须返回同一个散列码。

  2. 通过equals调用返回true 的2个对象的hashCode一定一样。

  3. 通过equasl返回false 的2个对象的散列码不需要不同,也就是他们的hashCode方法的返回值允许出现相同的情况。

总结一句话:等价的(调用equals返回true)对象必须产生相同的散列码。不等价的对象,不要求产生的散列码不相同。

所以我们写代码时一致约定;
重写了euqls方法的对象必须同时重写hashCode()方法
如果2个对象通过equals调用后返回是true,那么这个2个对象的hashCode方法也必须返回同样的int型散列码。
如果2个对象通过equals返回false,他们的hashCode返回的值允许相同。(然而,程序员必须意识到,hashCode返回独一无二的散列码,会让存储这个对象的hashtables更好地工作。)
在上面的例子中,Test类对象有2个字段,num和data,这2个字段代表了对象的状态,他们也用在equals方法中作为评判的依据。那么, 在hashCode方法中,这2个字段也要参与hash值的运算,作为hash运算的中间参数。这点很关键,这是为了遵守:2个对象equals,那么 hashCode一定相同规则。
也是说,参与equals函数的字段,也必须都参与hashCode 的计算

hashCode重写指南

重写原则:

  • 最终的hash是个int值,而不能溢出
  • 不同的对象的hash码应该尽量不同,避免hash冲突。

解决方案:

  1. 定义一个int类型的变量hash,初始化为任意非零常量整数值(例如 7)。
  2. 接下来让你认为重要的字段(equals中衡量相等的字段)参入散列运,算每一个重要字段都会产生一个hash分量,为最终的hash值做出贡献(影响)
重要字段var的类型它生成的分量
byte、char、short、intvar_code = (int)var;
longvar_code = (int)(var ^ (var >>> 32));
floatvar_code = Float.floatToIntBits(var);
doublelong bits = Double.doubleToLongBits(var); var_code = (int)(bits ^ (bits >>> 32));
booleanvar_code = var ? 1 : 0;
对象引用var_code = (null == var ? 0 : var.hashCode());
  1. 合并,选择一个倍乘的数字 31,把所有的分量都总和起来
int hash = 7;
hash = 31 * hash + var_code1;
hash = 31 * hash + var_code2;
...

上面例子中的hashCode方法重写如下:

@Override
public int hashCode() {
   int hash = 7;
   hash = hash * 31 + this.num;
   hash = hash * 31 + this.data.hashCode();
   return hash;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值