equals和hashcode

我在使用findbugs的时候,发现了自己程序中的一个bug:
EQ_SELF_USE_OBJECT
Bug: defines equals method and uses Object.equals(Object)
这说的是在写.equals函数的时候,传递的参数需要用object类,否则就不是重写,不能覆盖父类的equals。
于是我想到我对equals和hashcode这点当时就没怎么学明白。想要梳理一下。
阅读:《Java编程思想》17.9散列与散列码
MIT6.031讲义 Reading 15: Equality : http://web.mit.edu/6.031/www/sp17/classes/15-equality/

1.equals

在一个自定义的ADT里面,我们需要判断相等,现在写一个简单的程序

1 package test1;
2
3 public class bag {
4 String label;
5
6 bag(String label) {
7 this.label = label;
8 }
9
10 public boolean equals(bag that) {
11 return this.label.equals(that.label);
12 }
13
14 public static void main(String[] args) {
15 bag bag1 = new bag(“CHANEL”);
16 bag bag2 = new bag(“CHANEL”);
17 Object bag3 = bag2;
18 bag bag4 = new bag(“PRADA”);
19 System.out.println(bag1.equals(bag2)); //true
20 System.out.println(bag1.equals(bag3)); //false
21 }
22 }
  事实上, bag 只是重载(overloaded)了 equals() ⽅法,因为它的⽅法标识和 Object 中的不⼀样,也就是说,这是 bag 中有两个 equals() ⽅法:⼀个是从 Object 隐式继承下来 的 equals(Object) ,还有⼀个就是我们写的 equals(bag).而正确的做法是重写(override),这也就是为什么我们重写的时候最好在开头加上@标记,这样就可以自动检查我们是否真的是override了。

package test1;

public class bag {
String label;

bag(String label) {
    this.label = label;
}

@Override

public boolean equals(Object that) {
    return (that instanceof bag) && this.sameValue((bag) that);
}

boolean sameValue(bag that1) {
    return this.label == that1.label;
}

public static void main(String[] args) {
    bag bag1 = new bag("CHANEL");
    bag bag2 = new bag("CHANEL");
    Object bag3 = bag2;
    bag bag4 = new bag("PRADA");
    System.out.println(bag1.equals(bag2));//true
    System.out.println(bag1.equals(bag3));//true
}

}

这就是我用findbugs找出的bug
另外,equals需满足四个条件,不然就会有潜在的bug:
1.equals 必须定义⼀个等价关系。即(⾃反性、对称性和传递性)。
2.equals 必须是确定的。x.equals(y)无论调用多少次都应该是相同的结果。
3.对于不是null的索引 x , x.equals(null) 一定返回false。
4.如果两个对象使⽤ equals 操作后结果为真,那么它们各⾃的 hashCode 操作的结果也应该相同。
前三条我们可以值观理解的,而为什么它们的hashcode需要相同,这就与ADT的查询有关了。

2.hashcode
1 bag bag1 = new bag(“CHANEL”);
2 bag bag2 = new bag(“CHANEL”);
3 Object bag3 = bag2;
4 bag bag4 = new bag(“PRADA”);
5 System.out.println(bag1.hashCode()); //118352462
6 System.out.println(bag2.hashCode()); //1550089733
7 System.out.println(bag3.hashCode()); //1550089733
8 Set set = new HashSet();
9 set.add(bag1);
10 System.out.println(set.contains(bag1)); //true
11 System.out.println(set.contains(bag2)); //false
  这就很奇怪,按照我们之前写到equals,bag1是和bag2相等的,所以set.contains(bag2)应该也为true才是。这就涉及到了hash类(hashSet,hashMap)的查询原理了。
查询一个值的过程首先是根据hashcode计算散列值,然后使用散列值查询数组。(数组中保存list,使用equals进行线性查询),所以hashcode可以相同,但是最好的状态是均匀分布这样更快。
这就是hashMap查询很快的原因了。
改正上面的程序:

@Override
public boolean equals(Object that) {
    return (that instanceof bag) && this.sameValue((bag) that);
}

boolean sameValue(bag that1) {
    return this.label == that1.label;
}
public int hashCode()
{
    return this.label.hashCode();
}
public static void main(String[] args) {
    bag bag1 = new bag("CHANEL");
    bag bag2 = new bag("CHANEL");
    Object bag3 = bag2;
    bag bag4 = new bag("PRADA");
    System.out.println(bag1.hashCode());   //1986660217
    System.out.println(bag2.hashCode());   //1986660217
    System.out.println(bag3.hashCode());   //1986660217
    Set<bag> set = new HashSet<bag>();
    set.add(bag1);
    System.out.println(set.contains(bag1));  //true
    System.out.println(set.contains(bag2));  //true    
}

其实这些细节什么的,不注意的话,也许没什么问题,但也许作为程序潜在的bug,也许会产生很严重的后果。

但是还有一个问题就是,如果这个变量是mutable的话(我添加了一个改label的mutator),那么我修改了里面的内容的话,那么hashSet里面就找不到了

public void changelabel(String string)
{
this.label =string;
}
public static void main(String[] args) {
bag bag1 = new bag(“CHANEL”);
bag bag2 = new bag(“CHANEL”);
Object bag3 = bag2;
bag bag4 = new bag(“PRADA”);
System.out.println(bag1.hashCode()); //1986660217
System.out.println(bag2.hashCode()); //1986660217
System.out.println(bag3.hashCode()); //1986660217
Set set = new HashSet();
set.add(bag1);
System.out.println(set.contains(bag1)); //true
bag1.changelabel(“DIOR”);
System.out.println(set.contains(bag1)); //false
}

所以对于可变数据类型,我们就不要改了,让equals() 应该⽐较索引,就像 == ⼀样即⽐较⾏为相等性就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值