zoukankan      html  css  js  c++  java
  • 重写equal()时为什么也得重写hashCode()之深度解读equal方法与hashCode方法渊源

    今天这篇文章我们打算来深度解读一下equal方法以及其关联方法hashCode(),我们准备从以下几点入手分析:


     

    1.equals()的所属以及内部原理(即Object中equals方法的实现原理)

      说起equals方法,我们都知道是超类Object中的一个基本方法,用于检测一个对象是否与另外一个对象相等。而在Object类中这个方法实际上是判断两个对象是否具有相同的引用,如果有,它们就一定相等。其源码如下:

     

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

     

      实际上我们知道所有的对象都拥有标识(内存地址)和状态(数据),同时“==”比较两个对象的的内存地址,所以说 Object 的 equals() 方法是比较两个对象的内存地址是否相等,即若 object1.equals(object2) 为 true,则表示 equals1 和 equals2 实际上是引用同一个对象。

    2.equals()与‘==’的区别

      或许这是我们面试时更容易碰到的问题“equals方法与‘==’运算符有什么区别?”,并且常常我们都会胸有成竹地回答:“equals比较的是对象的内容,而‘==’比较的是对象的地址。”。但是从前面我们可以知道equals方法在Object中的实现也是间接使用了‘==’运算符进行比较的,所以从严格意义上来说,我们前面的回答并不完全正确。我们先来看一段代码并运行再来讨论这个问题。

     

     1     package com.zejian.test;  
     2     public class Car {  
     3         private int batch;  
     4         public Car(int batch) {  
     5             this.batch = batch;  
     6         }  
     7         public static void main(String[] args) {  
     8             Car c1 = new Car(1);  
     9             Car c2 = new Car(1);  
    10           System.out.println(c1.equals(c2));  
    11             System.out.println(c1 == c2);  
    12         }  
    13     }  

     

    运行结果:

    false

    false

    分析:对于‘==’运算符比较两个Car对象,返回了false,这点我们很容易明白,毕竟它们比较的是内存地址,而c1与c2是两个不同的对象,所以c1与c2的内存地址自然也不一样。现在的问题是,我们希望生产的两辆的批次(batch)相同的情况下就认为这两辆车相等,但是运行的结果是尽管c1与c2的批次相同,但equals的结果却反回了false。当然对于equals返回了false,我们也是心知肚明的,因为equal来自Object超类,访问修饰符为public,而我们并没有重写equal方法,故调用的必然是Object超类的原始方equals方法,根据前面分析我们也知道该原始equal方法内部实现使用的是'=='运算符,所以返回了false。因此为了达到我们的期望值,我们必须重写Car的equal方法,让其比较的是对象的批次(即对象的内容),而不是比较内存地址,于是修改如下:

     

    1       @Override  
    2         public boolean equals(Object obj) {  
    3             if (obj instanceof Car) {  
    4                 Car c = (Car) obj;  
    5                 return batch == c.batch;  
    6             }  
    7             return false;  
    8         }  

     

    使用instanceof来判断引用obj所指向的对象的类型,如果obj是Car类对象,就可以将其强制转为Car对象,然后比较两辆Car的批次,相等返回true,否则返回false。当然如果obj不是 Car对象,自然也得返回false。我们再次运行:

    true

    false

    嗯,达到我们预期的结果了。因为前面的面试题我们应该这样回答更佳
    总结:默认情况下也就是从超类Object继承而来的equals方法与‘==’是完全等价的,比较的都是对象的内存地址,但我们可以重写equals方法,使其按照我们的需求的方式进行比较,如String类重写了equals方法,使其比较的是字符的序列,而不再是内存地址。

    3.equals()的重写规则

    前面我们已经知道如何去重写equals方法来实现我们自己的需求了,但是我们在重写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。

    当然在通常情况下,如果只是进行同一个类两个对象的相等比较,一般都可以满足以上5点要求,下面我们来看前面写的一个例子。

     

     1     package com.zejian.test;  
     2     public class Car {  
     3         private int batch;  
     4         public Car(int batch) {  
     5             this.batch = batch;  
     6         }  
     7         public static void main(String[] args) {  
     8             Car c1 = new Car(1);  
     9             Car c2 = new Car(1);  
    10             Car c3 = new Car(1);  
    11             System.out.println("自反性->c1.equals(c1):" + c1.equals(c1));  
    12             System.out.println("对称性:");  
    13           System.out.println(c1.equals(c2));  
    14           System.out.println(c2.equals(c1));  
    15             System.out.println("传递性:");  
    16           System.out.println(c1.equals(c2));  
    17           System.out.println(c2.equals(c3));  
    18           System.out.println(c1.equals(c3));  
    19             System.out.println("一致性:");  
    20             for (int i = 0; i < 50; i++) {  
    21                 if (c1.equals(c2) != c1.equals(c2)) {  
    22                     System.out.println("equals方法没有遵守一致性!");  
    23                     break;  
    24                 }  
    25             }  
    26             System.out.println("equals方法遵守一致性!");  
    27             System.out.println("与null比较:");  
    28             System.out.println(c1.equals(null));  
    29         }  
    30       @Override  
    31         public boolean equals(Object obj) {  
    32             if (obj instanceof Car) {  
    33                 Car c = (Car) obj;  
    34                 return batch == c.batch;  
    35             }  
    36             return false;  
    37         }  
    38     }  

     

    运行结果:

    自反性->c1.equals(c1):true

    对称性:

    true

    true

    传递性:

    true

    true

    true

    一致性:

    equals方法遵守一致性!

    与null比较:

    false

    由运行结果我们可以看出equals方法在同一个类的两个对象间的比较还是相当容易理解的。但是如果是子类与父类混合比较,那么情况就不太简单了。下面我们来看看另一个例子,首先,我们先创建一个新类BigCar,继承于Car,然后进行子类与父类间的比较。

     

     1     package com.zejian.test;  
     2     public class BigCar extends Car {  
     3         int count;  
     4         public BigCar(int batch, int count) {  
     5             super(batch);  
     6             this.count = count;  
     7         }  
     8       @Override  
     9         public boolean equals(Object obj) {  
    10             if (obj instanceof BigCar) {  
    11                 BigCar bc = (BigCar) obj;  
    12                 return super.equals(bc) && count == bc.count;  
    13             }  
    14             return false;  
    15         }  
    16         public static void main(String[] args) {  
    17             Car c = new Car(1);  
    18             BigCar bc = new BigCar(1, 20);  
    19           System.out.println(c.equals(bc));  
    20           System.out.println(bc.equals(c));  
    21         }  
    22     }  

     

    运行结果:

    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方法应该做如下修改:

     

    1    @Override  
    2     public boolean equals(Object obj) {  
    3         if (obj instanceof BigCar) {  
    4             BigCar bc = (BigCar) obj;  
    5             return super.equals(bc) && count == bc.count;  
    6         }  
    7         return super.equals(obj);  
    8     }  

     

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

     1     package com.zejian.test;  
     2     public class BigCar extends Car {  
     3         int count;  
     4         public BigCar(int batch, int count) {  
     5             super(batch);  
     6             this.count = count;  
     7         }  
     8         @Override  
     9         public boolean equals(Object obj) {  
    10             if (obj instanceof BigCar) {  
    11                 BigCar bc = (BigCar) obj;  
    12                 return super.equals(bc) && count == bc.count;  
    13             }  
    14             return super.equals(obj);  
    15         }  
    16         public static void main(String[] args) {  
    17             Car c = new Car(1);  
    18             BigCar bc = new BigCar(1, 20);  
    19             BigCar bc2 = new BigCar(1, 22);  
    20             System.out.println(bc.equals(c));  
    21             System.out.println(c.equals(bc2));  
    22             System.out.println(bc.equals(bc2));  
    23         }  
    24     }  

    运行结果:

    true

    true

    false

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

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

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

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

     

     1     package com.zejian.test;  
     2     public class Combination4BigCar {  
     3         private Car c;  
     4         private int count;  
     5         public Combination4BigCar(int batch, int count) {  
     6             c = new Car(batch);  
     7             this.count = count;  
     8         }  
     9       @Override  
    10         public boolean equals(Object obj) {  
    11             if (obj instanceof Combination4BigCar) {  
    12                 Combination4BigCar bc = (Combination4BigCar) obj;  
    13                 return c.equals(bc.c) && count == bc.count;  
    14             }  
    15             return false;  
    16         }  
    17     }  

     

    从代码来看即使batch相同,Combination4BigCar类的对象与Car类的对象间的比较也永远都是false,但是这样看起来也就合情合理了,毕竟Combination4BigCar也不是Car的子类,因此equals方法也就没必要提供任何对Car的比较支持,同时也不会违背了equals方法的传递性。

    4.equals()的重写规则之必要性深入解读

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

     

     1     package com.zejian.test;  
     2     import java.util.ArrayList;  
     3     import java.util.List;  
     4     /** * 反面例子 * @author zejian */  
     5     public class AbnormalResult {  
     6         public static void main(String[] args) {  
     7             List<A> list = new ArrayList<A>();  
     8             A a = new A();  
     9             B b = new B();  
    10           list.add(a);  
    11             System.out.println("list.contains(a)->" + list.contains(a));  
    12             System.out.println("list.contains(b)->" + list.contains(b));  
    13           list.clear();  
    14           list.add(b);  
    15             System.out.println("list.contains(a)->" + list.contains(a));  
    16             System.out.println("list.contains(b)->" + list.contains(b));  
    17         }  
    18         static class A {  
    19           @Override  
    20             public boolean equals(Object obj) {  
    21                 return obj instanceof A;  
    22             }  
    23         }  
    24         static class B extends A {  
    25           @Override  
    26             public boolean equals(Object obj) {  
    27                 return obj instanceof B;  
    28             }  
    29         }  
    30     }  

     

    上面的代码,我们声明了 A,B两个类,注意必须是static,否则无法被main调用。B类继承A,两个类都重写了equals方法,但是根据我们前面的分析,这样重写是没有遵守对称性原则的,我们先来看看运行结果:

    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方法内部实现

     

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

     

    进入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方法修改一下就可以了。

    1     static class B extends A{  
    2           @Override  
    3             public boolean equals(Object obj) {  
    4                 if(obj instanceof B){  
    5                     return true;  
    6                 }  
    7                 return super.equals(obj);  
    8             }  
    9         }  

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

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

      这个问题之前我也很好奇,不过最后还是在书上得到了比较明朗的解释,当然这个问题主要是针对映射相关的操作(Map接口)。学过数据结构的同学都知道Map接口的类会使用到键对象的哈希码,当我们调用put方法或者get方法对Map容器进行操作时,都是根据键对象的哈希码来计算存储位置的,因此如果我们对哈希码的获取没有相关保证,就可能会得不到预期的结果。在java中,我们可以使用hashCode()来获取对象的哈希码,其值就是对象的存储地址,这个方法在Object类中声明,因此所有的子类都含有该方法。那我们先来认识一下hashCode()这个方法吧。hashCode的意思就是散列码,也就是哈希码,是由对象导出的一个整型值,散列码是没有规律的,如果x与y是两个不同的对象,那么x.hashCode()与y.hashCode()基本是不会相同的,下面通过String类的hashCode()计算一组散列码:

     

     1     package com.zejian.test;  
     2     public class HashCodeTest {  
     3         public static void main(String[] args) {  
     4             int hash=0;  
     5             String s="ok";  
     6             StringBuilder sb =new StringBuilder(s);  
     7               
     8             System.out.println(s.hashCode()+"  "+sb.hashCode());  
     9               
    10             String t = new String("ok");  
    11             StringBuilder tb =new StringBuilder(s);  
    12             System.out.println(t.hashCode()+"  "+tb.hashCode());  
    13         }  
    14           
    15     }  

     

    运行结果:

    3548  1829164700

    3548  2018699554

    我们可以看出,字符串s与t拥有相同的散列码,这是因为字符串的散列码是由内容导出的。而字符串缓冲sb与tb却有着不同的散列码,这是因为StringBuilder没有重写hashCode方法,它的散列码是由Object类默认的hashCode方法计算出来的对象存储地址,所以散列码自然也就不同了。那么我们该如何重写出一个较好的hashCode方法呢,其实并不难,我们只要合理地组织对象的散列码,就能够让不同的对象产生比较均匀的散列码。例如下面的例子:

     

     1     package com.zejian.test;  
     2     public class Model {  
     3         private String name;  
     4         private double salary;  
     5         private int sex;  
     6           
     7        @Override  
     8         public int hashCode() {  
     9             return name.hashCode()+new Double(salary).hashCode()   
    10                     + new Integer(sex).hashCode();  
    11         }  
    12     }  

     

    上面的代码我们通过合理的利用各个属性对象的散列码进行组合,最终便能产生一个相对比较好的或者说更加均匀的散列码,当然上面仅仅是个参考例子而已,我们也可以通过其他方式去实现,只要能使散列码更加均匀(所谓的均匀就是每个对象产生的散列码最好都不冲突)就行了。不过这里有点要注意的就是java 7中对hashCode方法做了两个改进,首先java发布者希望我们使用更加安全的调用方式来返回散列码,也就是使用null安全的方法Objects.hashCode(注意不是Object而是java.util.Objects)方法,这个方法的优点是如果参数为null,就只返回0,否则返回对象参数调用的hashCode的结果。Objects.hashCode 源码如下:

    1     public static int hashCode(Object o) {  
    2             return o != null ? o.hashCode() : 0;  
    3     }  

    因此我们修改后的代码如下:

     1     package com.zejian.test;  
     2     import java.util.Objects;  
     3     public  class Model {  
     4         private   String name;  
     5         private double salary;  
     6         private int sex;  
     7         @Override  
     8         public int hashCode() {  
     9             return Objects.hashCode(name)+new Double(salary).hashCode()   
    10                     + new Integer(sex).hashCode();  
    11         }  
    12     }  

    java 7还提供了另外一个方法java.util.Objects.hash(Object... objects),当我们需要组合多个散列值时可以调用该方法。进一步简化上述的代码:

     1     package com.zejian.test;  
     2     import java.util.Objects;  
     3     public  class Model {  
     4         private   String name;  
     5         private double salary;  
     6         private int sex;  
     7     //  @Override  
     8     //  public int hashCode() {  
     9     //      return Objects.hashCode(name)+new Double(salary).hashCode()   
    10     //              + new Integer(sex).hashCode();  
    11     //  }  
    12           
    13       @Override  
    14         public int hashCode() {  
    15             return Objects.hash(name,salary,sex);  
    16         }  
    17     }  

    好了,到此hashCode()该介绍的我们都说了,还有一点要说的如果我们提供的是一个数值类型的变量的话,那么我们可以调用Arrays.hashCode()来计算它的散列码,这个散列码是由数组元素的散列码组成的。接下来我们回归到我们之前的问题,重写equals方法时也必须重写hashCode方法。在Java API文档中关于hashCode方法有以下几点规定(原文来自java深入解析一书)。

    • 在java应用程序执行期间,如果在equals方法比较中所用的信息没有被修改,那么在同一个对象上多次调用hashCode方法时必须一致地返回相同的整数。如果多次执行同一个应用时,不要求该整数必须相同。

    • 如果两个对象通过调用equals方法是相等的,那么这两个对象调用hashCode方法必须返回相同的整数。

    • 如果两个对象通过调用equals方法是不相等的,不要求这两个对象调用hashCode方法必须返回不同的整数。但是程序员应该意识到对不同的对象产生不同的hash值可以提供哈希表的性能。

    通过前面的分析,我们知道在Object类中,hashCode方法是通过Object对象的地址计算出来的,因为Object对象只与自身相等,所以同一个对象的地址总是相等的,计算取得的哈希码也必然相等,对于不同的对象,由于地址不同,所获取的哈希码自然也不会相等。因此到这里我们就明白了,如果一个类重写了equals方法,但没有重写hashCode方法,将会直接违法了第2条规定,这样的话,如果我们通过映射表(Map接口)操作相关对象时,就无法达到我们预期想要的效果。如果大家不相信, 可以看看下面的例子(来自java深入解析一书)

     

     1     package com.zejian.test;  
     2     import java.util.HashMap;  
     3     import java.util.Map;  
     4     public class MapTest {  
     5         public static void main(String[] args) {  
     6             Map<String,Value> map1 = new HashMap<String,Value>();  
     7             String s1 = new String("key");  
     8             String s2 = new String("key");    
     9             Value value = new Value(2);  
    10           map1.put(s1, value);  
    11             System.out.println("s1.equals(s2):"+s1.equals(s2));  
    12             System.out.println("map1.get(s1):"+map1.get(s1));  
    13             System.out.println("map1.get(s2):"+map1.get(s2));  
    14               
    15               
    16             Map<Key,Value> map2 = new HashMap<Key,Value>();  
    17             Key k1 = new Key("A");  
    18             Key k2 = new Key("A");  
    19           map2.put(k1, value);  
    20             System.out.println("k1.equals(k2):"+s1.equals(s2));  
    21             System.out.println("map2.get(k1):"+map2.get(k1));  
    22             System.out.println("map2.get(k2):"+map2.get(k2));  
    23         }  
    24           
    25         /** 
    26          * 键 
    27          * @author zejian 
    28          * 
    29          */  
    30         static class Key{  
    31             private String k;  
    32             public Key(String key){  
    33                 this.k=key;  
    34             }  
    35               
    36           @Override  
    37             public boolean equals(Object obj) {  
    38                 if(obj instanceof Key){  
    39                     Key key=(Key)obj;  
    40                     return k.equals(key.k);  
    41                 }  
    42                 return false;  
    43             }  
    44         }  
    45           
    46         /** 
    47          * 值 
    48          * @author zejian 
    49          * 
    50          */  
    51         static class Value{  
    52             private int v;  
    53               
    54             public Value(int v){  
    55                 this.v=v;  
    56             }  
    57               
    58           @Override  
    59             public String toString() {  
    60                 return "类Value的值-->"+v;  
    61             }  
    62         }  
    63     }  

     

    代码比较简单,我们就不过多解释了(注意Key类并没有重写hashCode方法),直接运行看结果

    1     s1.equals(s2):true  
    2     map1.get(s1):类Value的值-->2  
    3     map1.get(s2):类Value的值-->2  
    4     k1.equals(k2):true  
    5     map2.get(k1):类Value的值-->2  
    6     map2.get(k2):null  

    对于s1和s2的结果,我们并不惊讶,因为相同的内容的s1和s2获取相同内的value这个很正常,因为String类重写了equals方法和hashCode方法,使其比较的是内容和获取的是内容的哈希码。但是对于k1和k2的结果就不太尽人意了,k1获取到的值是2,k2获取到的是null,这是为什么呢?想必大家已经发现了,Key只重写了equals方法并没有重写hashCode方法,这样的话,equals比较的确实是内容,而hashCode方法呢?没重写,那就肯定调用超类Object的hashCode方法,这样返回的不就是地址了吗?k1与k2属于两个不同的对象,返回的地址肯定不一样,所以现在我们知道调用map2.get(k2)为什么返回null了吧?那么该如何修改呢?很简单,我们要做也重写一下hashCode方法即可(如果参与equals方法比较的成员变量是引用类型的,则可以递归调用hashCode方法来实现):

    1    @Override  
    2     public int hashCode() {  
    3          return k.hashCode();  
    4     }  

    再次运行:

    1     s1.equals(s2):true  
    2     map1.get(s1):类Value的值-->2  
    3     map1.get(s2):类Value的值-->2  
    4     k1.equals(k2):true  
    5     map2.get(k1):类Value的值-->2  
    6     map2.get(k2):类Value的值-->2  

    6.重写equals()中getClass与instanceof的区别

    虽然前面我们都在使用instanceof(当然前面我们是根据需求(批次相同即相等)而使用instanceof的),但是在重写equals() 方法时,一般都是推荐使用 getClass 来进行类型判断(除非所有的子类有统一的语义才使用instanceof),不是使用 instanceof。我们都知道 instanceof 的作用是判断其左边对象是否为其右边类的实例,返回 boolean 类型的数据。可以用来判断继承中的子类的实例是否为父类的实现。下来我们来看一个例子:父类Person

     

     1     public class Person {  
     2             protected String name;  
     3             public String getName() {  
     4                 return name;  
     5             }  
     6             public void setName(String name) {  
     7                 this.name = name;  
     8             }  
     9             public Person(String name){  
    10                 this.name = name;  
    11             }  
    12             public boolean equals(Object object){  
    13                 if(object instanceof Person){  
    14                     Person p = (Person) object;  
    15                     if(p.getName() == null || name == null){  
    16                         return false;  
    17                  }  
    18                     else{  
    19                         return name.equalsIgnoreCase(p.getName ());  
    20                    }  
    21                 }  
    22                 return false;  
    23            }  
    24         }  

     

    子类 Employee

     1     public class Employee extends Person{  
     2             private int id;  
     3             public int getId() {  
     4                 return id;  
     5             }  
     6             public void setId(int id) {  
     7                 this.id = id;  
     8             }  
     9             public Employee(String name,int id){  
    10                 super(name);  
    11                 this.id = id;  
    12             }  
    13             /** 
    14              * 重写equals()方法 
    15              */  
    16             public boolean equals(Object object){  
    17                 if(object instanceof Employee){  
    18                     Employee e = (Employee) object;  
    19                     return super.equals(object) && e.getId() == id;  
    20                 }  
    21                 return false;  
    22             }  
    23         }  

    上面父类 Person 和子类 Employee 都重写了 equals(),不过 Employee 比父类多了一个id属性,而且这里我们并没有统一语义。测试代码如下:

     1     public class Test {  
     2             public static void main(String[] args) {  
     3                 Employee e1 = new Employee("chenssy", 23);  
     4                 Employee e2 = new Employee("chenssy", 24);  
     5                 Person p1 = new Person("chenssy");  
     6              System.out.println(p1.equals(e1));  
     7              System.out.println(p1.equals(e2));  
     8              System.out.println(e1.equals(e2));  
     9           }  
    10         }  

    上面代码我们定义了两个员工和一个普通人,虽然他们同名,但是他们肯定不是同一人,所以按理来说结果应该全部是 false,但是事与愿违,结果是:true、true、false。对于那 e1!=e2 我们非常容易理解,因为他们不仅需要比较 name,还需要比较 ID。但是 p1 即等于 e1 也等于 e2,这是非常奇怪的,因为 e1、e2 明明是两个不同的类,但为什么会出现这个情况?首先 p1.equals(e1),是调用 p1 的 equals 方法,该方法使用 instanceof 关键字来检查 e1 是否为 Person 类,这里我们再看看 instanceof:判断其左边对象是否为其右边类的实例,也可以用来判断继承中的子类的实例是否为父类的实现。他们两者存在继承关系,肯定会返回 true 了,而两者 name 又相同,所以结果肯定是 true。所以出现上面的情况就是使用了关键字 instanceof,这是非常容易导致我们“钻牛角尖”。故在覆写 equals 时推荐使用 getClass 进行类型判断。而不是使用 instanceof(除非子类拥有统一的语义)。

    7.编写一个完美equals()的几点建议

    下面给出编写一个完美的equals方法的建议(出自Java核心技术 第一卷:基础知识):

    1)显式参数命名为otherObject,稍后需要将它转换成另一个叫做other的变量(参数名命名,强制转换请参考建议5)

    2)检测this与otherObject是否引用同一个对象 :if(this == otherObject) return true;(存储地址相同,肯定是同个对象,直接返回true)

    3) 检测otherObject是否为null ,如果为null,返回false.if(otherObject == null) return false;

    4) 比较this与otherObject是否属于同一个类 (视需求而选择)

    • 如果equals的语义在每个子类中有所改变,就使用getClass检测 :if(getClass()!=otherObject.getClass()) return false; (参考前面分析的第6点)

    • 如果所有的子类都拥有统一的语义,就使用instanceof检测 :if(!(otherObject instanceof ClassName)) return false;(即前面我们所分析的父类car与子类bigCar混合比,我们统一了批次相同即相等)

    5) 将otherObject转换为相应的类类型变量:ClassName other = (ClassName) otherObject;

    6) 现在开始对所有需要比较的域进行比较 。使用==比较基本类型域,使用equals比较对象域。如果所有的域都匹配,就返回true,否则就返回flase。

    • 如果在子类中重新定义equals,就要在其中包含调用super.equals(other)

    • 当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明 相等对象必须具有相等的哈希码 。

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    CSS3—— 2D转换 3D转换 过渡 动画
    CSS3——边框 圆角 背景 渐变 文本效果
    CSS3——表单 计数器 网页布局 应用实例
    CSS3——提示工具 图片廓 图像透明 图像拼接技术 媒体类型 属性选择器
    CSS3——对齐 组合选择符 伪类 伪元素 导航栏 下拉菜单
    CSS3——分组和嵌套 尺寸 display显示 position定位 overflow float浮动
    CSS3——盒子模型 border(边框) 轮廓(outline)属性 margin外边距 padding填充
    Eclipse连接数据库报错Local variable passwd defined in an enclosing scope must be final or effectively final
    数据库——单表查询
    数据库——添加,修改,删除
  • 原文地址:https://www.cnblogs.com/bsjl/p/8626553.html
Copyright © 2011-2022 走看看