zoukankan      html  css  js  c++  java
  • 解读equals()和hashCode()

    前面部分摘自:https://blog.csdn.net/javazejian/article/details/51348320 

    一:Object中equals方法的实现原理

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

    每个对象都有内存地址和数据,“==”比较2个对象的地址,Object类中的equals()比较的是2个对象的地址是否相同,object1.equals(object2) 为 true,则表示 equals1 和 equals2 实际上是引用同一个对象。

    二:equals与“==”的区别

    equals比较的是内容,“==”比较的是地址。

    很明显,这个说法是不完全的

    来看个例子

    package com.zejian.test;
    public class Car {
    private int batch;
    public Car(int batch) {
    this.batch = batch;
    }
    public static void main(String[] args) {
    Car c1 = new Car(1);
    Car c2 = new Car(1);
    System.out.println(c1.equals(c2));
    System.out.println(c1 == c2);
    }
    }

    console

    false

    false

    分析:“==”比较的是地址,所以是false,这个不用多说.但是为什么equals也是false呢?因为所以类都集成Object类,Car类没有重写Object类里面的equals方法,所以是使用父类的equals方法,return (this == obj); 比较的亦是对象的地址,那么怎么解决这个问题呢?

    那就是重写equals方法(比较内容)

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Car) {
            Car = (Car) obj;
            return batch == c.batch;
        }
        return false;
    }        

    三:equals的重写规则

    自反性。对于任何非null的引用值x,x.equals(x)应返回true。

    对称性。对于任何非null的引用值x与y,当且仅当:y.equals(x)返回true时,x.equals(y)才返回true。

    传递性。对于任何非null的引用值x、y与z,如果y.equals(x)返回true,y.equals(z)返回true,那么x.equals(z)也应返回true。

    一致性。对于任何非null的引用值x与y,假设对象上equals比较中的信息没有被修改,则多次调用x.equals(y)始终返回true或者始终返回false。

    对于任何非空引用值x,x.equal(null)应返回false。

    package com.zejian.test;
    public class Car {
        private int batch;
        public Car(int batch) {
            this.batch = batch;
        }
        public static void main(String[] args) {
            Car c1 = new Car(1);
            Car c2 = new Car(1);
            Car c3 = new Car(1);
            System.out.println("自反性->c1.equals(c1):" + c1.equals(c1));
            System.out.println("对称性:");
            System.out.println(c1.equals(c2));
            System.out.println(c2.equals(c1));
            System.out.println("传递性:");
            System.out.println(c1.equals(c2));
            System.out.println(c2.equals(c3));
            System.out.println(c1.equals(c3));
            System.out.println("一致性:");
            for (int i = 0; i < 50; i++) {
                if (c1.equals(c2) != c1.equals(c2)) {
                    System.out.println("equals方法没有遵守一致性!");
                    break;
                }
            }
            System.out.println("equals方法遵守一致性!");
            System.out.println("与null比较:");
            System.out.println(c1.equals(null));
        }
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Car) {
                Car c = (Car) obj;
                return batch == c.batch;
            }
            return false;
        }
    }
    自反性->c1.equals(c1):true
    
    对称性:
    
    true
    
    true
    
    传递性:
    
    true
    
    true
    
    true
    
    一致性:
    
    equals方法遵守一致性!
    
    与null比较:
    
    false

    子类与父类混合比较

    package com.zejian.test;
    public class BigCar extends Car {
        int count;
        public BigCar(int batch, int count) {
            super(batch);
            this.count = count;
        }
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof BigCar) {
                BigCar bc = (BigCar) obj;
                return super.equals(bc) && count == bc.count;
            }
            return false;
        }
        public static void main(String[] args) {
            Car c = new Car(1);
            BigCar bc = new BigCar(1, 20);
            System.out.println(c.equals(bc));
            System.out.println(bc.equals(c));
        }
    }

    console:

    true

    false

    对于这样的结果,自然是我们意料之中的啦。因为BigCar类型肯定是属于Car类型,所以c.equals(bc)肯定为true,对于bc.equals(c)返回false,是因为Car类型并不一定是BigCar类型(Car类还可以有其他子类)。嗯,确实是这样。但如果有这样一个需求,只要BigCar和Car的生产批次一样,我们就认为它们两个是相当的,在这样一种需求的情况下,父类(Car)与子类(BigCar)的混合比较就不符合equals方法对称性特性了。很明显一个返回true,一个返回了false,根据对称性的特性,此时两次比较都应该返回true才对。那么该如何修改才能符合对称性呢?其实造成不符合对称性特性的原因很明显,那就是因为Car类型并不一定是BigCar类型(Car类还可以有其他子类),在这样的情况下(Car instanceof BigCar)永远返回false,因此,我们不应该直接返回false,而应该继续使用父类的equals方法进行比较才行(因为我们的需求是批次相同,两个对象就相等,父类equals方法比较的就是batch是否相同)。因此BigCar的equals方法应该做如下修改:

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof BigCar) {
            BigCar bc = (BigCar) obj;
            return super.equals(bc) && count == bc.count;
        }
        return super.equals(obj);
    }

    这样运行的结果就都为true了。但是到这里问题并没有结束,虽然符合了对称性,却还没符合传递性,实例如下:

    package com.zejian.test;
    public class BigCar extends Car {
        int count;
        public BigCar(int batch, int count) {
            super(batch);
            this.count = count;
        }
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof BigCar) {
                BigCar bc = (BigCar) obj;
                return super.equals(bc) && count == bc.count;
            }
            return super.equals(obj);
        }
        public static void main(String[] args) {
            Car c = new Car(1);
            BigCar bc = new BigCar(1, 20);
            BigCar bc2 = new BigCar(1, 22);
            System.out.println(bc.equals(c));
            System.out.println(c.equals(bc2));
            System.out.println(bc.equals(bc2));
        }
    }

    console:

    true

    true

    false

    bc,bc2,c的批次都是相同的,按我们之前的需求应该是相等,而且也应该符合equals的传递性才对。但是事实上运行结果却不是这样,违背了传递性。出现这种情况根本原因在于:

    父类与子类进行混合比较。

    子类中声明了新变量,并且在子类equals方法使用了新增的成员变量作为判断对象是否相等的条件。

    只要满足上面两个条件,equals方法的传递性便失效了。而且目前并没有直接的方法可以解决这个问题。因此我们在重写equals方法时这一点需要特别注意。虽然没有直接的解决方法,但是间接的解决方案还说有滴,那就是通过组合的方式来代替继承,还有一点要注意的是组合的方式并非真正意义上的解决问题(只是让它们间的比较都返回了false,从而不违背传递性,然而并没有实现我们上面batch相同对象就相等的需求),而是让equals方法满足各种特性的前提下,让代码看起来更加合情合理,代码如下:

    package com.zejian.test;
    public class Combination4BigCar {
        private Car c;
        private int count;
        public Combination4BigCar(int batch, int count) {
            c = new Car(batch);
            this.count = count;
        }
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Combination4BigCar) {
                Combination4BigCar bc = (Combination4BigCar) obj;
                return c.equals(bc.c) && count == bc.count;
            }
            return false;
        }
    }

    四:equals深入解读

    前面我们一再强调了equals方法重写必须遵守的规则,接下来我们就是分析一个反面的例子,看看不遵守这些规则到底会造成什么样的后果。

    package com.zejian.test;
    import java.util.ArrayList;
    import java.util.List;
    /** * 反面例子 * @author zejian */
    public class AbnormalResult {
        public static void main(String[] args) {
            List<A> list = new ArrayList<A>();
            A a = new A();
            B b = new B();
            list.add(a);
            System.out.println("list.contains(a)->" + list.contains(a));
            System.out.println("list.contains(b)->" + list.contains(b));
            list.clear();
            list.add(b);
            System.out.println("list.contains(a)->" + list.contains(a));
            System.out.println("list.contains(b)->" + list.contains(b));
        }
        static class A {
            @Override
            public boolean equals(Object obj) {
                return obj instanceof A;
            }
        }
        static class B extends A {
            @Override
            public boolean equals(Object obj) {
                return obj instanceof B;
            }
        }
    }

    console:

    list.contains(a)->true

    list.contains(b)->false

    list.contains(a)->true

    list.contains(b)->true

    19行和24行的输出没什么好说的,将a,b分别加入list中,list中自然会含有a,b。但是为什么20行和23行结果会不一样呢?我们先来看看contains方法内部实现

    @Override       
    public boolean contains(Object o) { 
         return indexOf(o) != -1; 
     }

    进入indexof方法

    @Override
    public int indexOf(Object o) {
        E[] a = this.a;
        if (o == null) {
            for (int i = 0; i < a.length; i++)
                if (a[i] == null)
                    return i;
        } else {
            for (int i = 0; i < a.length; i++)
                if (o.equals(a[i]))
                    return i;
        }
        return -1;
    }

    可以看出最终调用的是对象的equals方法,所以当调用20行代码list.contains(b)时,实际上调用了

    b.equals(a[i]),a[i]是集合中的元素集合中的类型而且为A类型(只添加了a对象),虽然B继承了A,但此时

    a[i] instanceof B

    结果为false,equals方法也就会返回false;而当调用23行代码list.contains(a)时,实际上调用了a.equal(a[i]),其中a[i]是集合中的元素而且为B类型(只添加了b对象),由于B类型肯定是A类型(B继承了A),所以

    a[i] instanceof A

    结果为true,equals方法也就会返回true,这就是整个过程。但很明显结果是有问题的,因为我们的 list的泛型是A,而B又继承了A,此时无论加入了a还是b,都属于同种类型,所以无论是contains(a),还是contains(b)都应该返回true才算正常。而最终却出现上面的结果,这就是因为重写equals方法时没遵守对称性原则导致的结果,如果没遵守传递性也同样会造成上述的结果。当然这里的解决方法也比较简单,我们只要将B类的equals方法修改一下就可以了。

    static class B extends A{
        @Override
        public boolean equals(Object obj) {
            if(obj instanceof B){
                return true;
            }
            return super.equals(obj);
        }
    }

    到此,我们也应该明白了重写equals必须遵守几点原则的重要性了。当然这里不止是list,只要是java集合类或者java类库中的其他方法,重写equals不遵守5点原则的话,都可能出现意想不到的结果。

    五.为什么重写equals()的同时还得重写hashCode()

    一个很常见的错误根源在于没有覆盖hashCode方法。在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap、HashSet和Hashtable。

    1.在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。

    2.如果两个对象根据equals()方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。

    3.如果两个对象根据equals()方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生相同的整数结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。

    六、eqauls方法和hashCode方法关系

    (1)同一对象上多次调用hashCode()方法,总是返回相同的整型值。

    (2)如果a.equals(b),则一定有a.hashCode() 一定等于 b.hashCode()。 

    (3)如果!a.equals(b),则a.hashCode() 不一定等于 b.hashCode()。此时如果a.hashCode() 总是不等于 b.hashCode(),会提高hashtables的性能。

    (4)a.hashCode()==b.hashCode() 则 a.equals(b)可真可假

    (5)a.hashCode()!= b.hashCode() 则 a.equals(b)为假。 

    关于这两个方法的重要规范: 

    规范1:若重写equals(Object obj)方法,有必要重写hashcode()方法,确保通过equals(Object obj)方法判断结果为true的两个对象具备相等的hashcode()返回值。说得简单点就是:“如果两个对象相同,那么他们的hashcode应该相等”。不过请注意:这个只是规范,如果你非要写一个类让equals(Object obj)返回true而hashcode()返回两个不相等的值,编译和运行都是不会报错的。不过这样违反了Java规范,程序也就埋下了BUG。 

    规范2:如果equals(Object obj)返回false,即两个对象“不相同”,并不要求对这两个对象调用hashcode()方法得到两个不相同的数。说的简单点就是:“如果两个对象不相同,他们的hashcode可能相同”。 

  • 相关阅读:
    亿级 Web 系统搭建:单机到分布式集群
    机器学习14种常见算法
    Nginx——使用 Nginx 提升网站访问速度【转载+整理】
    全栈开发工程师,就是个神话~【转载+整理】
    谷歌、亚马逊相继宣布屏蔽 Flash 广告,又一个时代行将结束?【转载+整理】
    前端框架现状调查【转载+整理】
    Swift 编程语言【转载+整理】
    如何将 Java 项目转换成 Maven 项目
    Eclipse 4.5.0 离线安装 Veloeclipse 插件
    Java 8 新特性——Lambdas 表达式
  • 原文地址:https://www.cnblogs.com/xhlwjy/p/11245105.html
Copyright © 2011-2022 走看看