zoukankan      html  css  js  c++  java
  • 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里面,我们需要判断相等,现在写一个简单的程序

    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() 应该⽐较索引,就像 == ⼀样即⽐较⾏为相等性就可以了。

  • 相关阅读:
    PHP运行模式
    Facebook揭秘HipHop项目 PHP程序大提速
    linux下django部署到apache
    流式传输的两大主流种类及流式传输特点
    深入理解php底层:php生命周期
    [转]PHP函数的实现原理及性能分析 .
    使用printk进行内核调试的方法
    探究PHP底层
    Apc缓存Opcode
    硬盘的读写原理
  • 原文地址:https://www.cnblogs.com/blairwaldorf/p/9095463.html
Copyright © 2011-2022 走看看