我在使用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里面,我们需要判断相等,现在写一个简单的程序

package test1;

public class bag {
	String label;

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

	public boolean equals(bag that) {
		return this.label.equals(that.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)); //false
	}
}

  事实上, 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

		bag bag1 = new bag("CHANEL");
		bag bag2 = new bag("CHANEL");
		Object bag3 = bag2;
		bag bag4 = new bag("PRADA");
		System.out.println(bag1.hashCode());   //118352462
		System.out.println(bag2.hashCode());   //1550089733
		System.out.println(bag3.hashCode());   //1550089733
		Set<bag> set = new HashSet<bag>();
		set.add(bag1);
		System.out.println(set.contains(bag1));  //true
		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<bag> set = new HashSet<bag>();
        set.add(bag1);
        System.out.println(set.contains(bag1));  //true
        bag1.changelabel("DIOR");
        System.out.println(set.contains(bag1));  //false 
    }

 

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

posted on 2018-05-27 21:34  暮雨煙深淺  阅读(184)  评论(0编辑  收藏  举报