Java基礎(六)判斷兩個對象相等:equals、hashcode、toString方法


  1.equal方法

  Object類中的equal方法用於檢測一個對象是否等於另外一個對象。在Object類中,這個方法將判斷兩個對象是否具有相同的引用。如果兩個對象具有相同的引用,它們一定是相等的。然而對於多數類來說,這種判斷並沒有什么意義,因為經常需要檢測兩個對象狀態是否相等,如果兩個對象的狀態相等,就認為這兩個狀態是相等的。 

  先來看一下Object類中的equals方法的源碼:

     * @param a an object
     * @param b an object to be compared with {@code a} for equality
     * @return {@code true} if the arguments are equal to each other
     * and {@code false} otherwise
     * @see Object#equals(Object)
     */
    public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }

  通過@see這個注釋,再去看一下調用的equals方法

     * @param   obj   the reference object with which to compare.
     * @return  {@code true} if this object is the same as the obj
     *          argument; {@code false} otherwise.
     * @see     #hashCode()
     * @see     java.util.HashMap
     */
    public boolean equals(Object obj) {
        return (this == obj);
    }

  下面有一個實際的例子,Employee類是Manager類的父類,那么根據現實中的情況,如何判斷兩個雇員是否相等呢,那肯定是如果兩個雇員對象的姓名、薪水和雇佣日期都一樣,就認為他們是相等的。(為什么不會有同年同月同日...你是個杠精把)

  看一下Employee類中判斷兩個雇員是否相等的方法:

   public boolean equals(Object otherObject)
   {
      // a quick test to see if the objects are identical
      if (this == otherObject) return true;

      // must return false if the explicit parameter is null
      if (otherObject == null) return false;

      // if the classes don't match, they can't be equal
      if (getClass() != otherObject.getClass()) return false;

      // now we know otherObject is a non-null Employee
      Employee other = (Employee) otherObject;

      // test whether the fields have identical values
      return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay);
   }

  如果這兩個對象相等,那么肯定相等。如果傳進來的對象變量為null,那么肯定不相等。如果兩個對象不屬於同一個類,那么肯定不相等。最后一種情況就很關鍵了,可能會有兩個對象變量的name和hireDay都為null的情況,這個時候就需要調用Object的equals方法了,通過源碼可以看出,

  • 如果兩個對象變量的name或hireDay都為null,則調用Objects.equals方法會返回true,
  • 如果其中只有一個對象變量的name或hireDay為null,即Objects.equals(a, b)中只有一個為null,那直接返回false
  • 如果兩個對象變量的name或hireDay都不為null,即Objects.equals(a, b)中兩個都不為null,這時根據return (a == b) || (a != null && a.equals(b));可以知道需要調用a.equals(b)方法判斷兩個域是否相等。

  子類Manager類中的equals方法為:即如果父類的equals方法檢測不相等,那么對象就不可能相等。如果父類中的域都相等,就繼續檢測子類中的實例域。

   public boolean equals(Object otherObject)
   {
      if (!super.equals(otherObject)) return false;
      Manager other = (Manager) otherObject;
      // super.equals checked that this and other belong to the same class
      return bonus == other.bonus;
   }

  測試代碼和輸出:

   public static void main(String[] args)
   {
      Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15);
      Employee alice2 = alice1;
      Employee alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15);
      Employee bob = new Employee("Bob Brandson", 50000, 1989, 10, 1);

      System.out.println("alice1 == alice2: " + (alice1 == alice2));

      System.out.println("alice1 == alice3: " + (alice1 == alice3));

      System.out.println("alice1.equals(alice3): " + alice1.equals(alice3));

      System.out.println("alice1.equals(bob): " + alice1.equals(bob));

      Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      boss.setBonus(5000);
System.out.println("carl.equals(boss): " + carl.equals(boss)); }

輸出為:
alice1 == alice2: true
alice1 == alice3: false
alice1.equals(alice3): true
alice1.equals(bob): false
carl.equals(boss): false

  這里有一個問題,就是如果是通過if (getClass() != otherObject.getClass()) return false;來判斷,如果兩個對象不在一個類中,那么就判斷稱這兩個對象不相等。但是如果兩個參數不屬於同一個類,該如何處理呢,如果修改成if (!otherObject instanceof Employee) return false;(A instance B的意思是,如果A是B的一個實例或B的子類的一個實例,就返回true,否則返回false)這樣的話如果要滿足對稱性的要求的話會有一些問題,例如,e.equals(m)中的e是一個Employee對象,m是一個Manager對象,並且兩個對象具有相同的姓名、薪水和雇佣日期,如果在Employee.equals中調用instanceof檢測,則返回true,這沒問題。但是如果要反過來的話,如果要讓m.equals(e)也返回true,這就要求Manager類的equals方法必須能夠用自己與任何一個Employee對象進行比較,然而Manager類的對象還具有Employee類的對象沒有的獨特的實例域,這就使得使用instanceof出現了問題。

  為了解決這個問題,可以從兩個方面考慮:(1)如果子類能夠擁有自己的相等概念,則對稱性需求將強制采用getClass進行檢測。(2)如果由父類決定相等的概念,那么就可以使用instanceof進行檢測,這樣可以在不同子類的對象之間進行相等的比較。

  例如:如果兩個Manager對象所對應的姓名、薪水和雇佣日期均相等,但是獎金不相等,就認為它們不相同,因此,可以使用getClass檢測。如果使用雇員的ID作為相等檢測的標准,並且這個相等的概念適用於所有的子類,就可以使用instanceof檢測,並應該將Employee.equals聲明為final。

  Java語言規范要求equals方法應該具有如下特性:

  • 自反性:對於任何非空引用x,x.equals(x)應該返回true
  • 對稱性:對於任何引用x和y,當且僅當y.equals(x)返回true,x.equals(y)也應該返回true
  • 傳遞性:對於任何引用x和y,如果x.equals(y)返回true,y.equals(z)返回true,則x.equals(z)也應該返回ture
  • 一致性:如果x和y引用的對象沒有發生變化,反復調用x.equals(y)應該返回同樣的結果
  • 對於任意非空引用x,x.equals(null)應該返回false

  一個完美的equals方法應該這樣編寫:

  (1)顯示參數命名為otherObject

  (2)檢測this與otherObject是否引用同一個對象:if(this == otherObject)  return true;

  (3)檢測otherObject是否為空:if(otherObject == null) return false;

  (4)比較this與otherObject是否屬於同一個類:

  • 如果equals的語義在每個子類中有所改變,就是用getClass檢測:if (getClass() != otherObject.getClass()) return false;
  • 如果所有的子類都擁有統一的語義,就是用instanceof檢測:if(!otherObject instanceof ClassName)) return false;

  (5)將otherObject轉換成相應的類類型變量:ClassName other = (ClassName)otherObject

  (6)對所有需要比較的域進行比較,使用“==”比較基本類型域,使用Object.equals方法比較對象域,如果所有的域都匹配,就返回true,否則,返回false

  (7)如果需要在子類中重新定義equals,要先調用父類的equals方法super.equals()

 

  2.hashCode方法

  散列碼(Hash Code)是由對象導出的一個整型值。散列碼是沒有規律的,如果x和y是兩個不同的對象,x.hashCode()與y.hashCode()基本上不會相同。

  這里必須強調的是:equals方法和hashCode的定義必須一致:如果x.equals(y)返回true,那么x.hashCode()和y.hashCode()必須相等。

  在Employee類中使用下面的代碼計算對象的hashCode值:為了實現兩個對象如果姓名、薪水和雇佣日期都相等那么這兩個對象相等,可以按照下面的 方法得到hashCode值,這里是因為相同字符串擁有相同的散列碼,因為字符串的散列碼是由內容導出的,有相同的計算公式。

   public int hashCode()
   {
      return Objects.hash(name, salary, hireDay); 
   }

  Objects.hash方法會對各個參數調用hashCode方法,並組合這些散列值。

  子類Manager類中的hashCode方法:

   public int hashCode()
   {
      return java.util.Objects.hash(super.hashCode(), bonus);
   }

  測試代碼和輸出:

   public static void main(String[] args)
   {
      Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15);
      Employee alice2 = alice1;
      Employee alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15);
      Employee bob = new Employee("Bob Brandson", 50000, 1989, 10, 1);
      Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      boss.setBonus(5000);

      System.out.println("alice1.hashCode(): " + alice1.hashCode());
      System.out.println("alice3.hashCode(): " + alice3.hashCode());
      System.out.println("bob.hashCode(): " + bob.hashCode());
      System.out.println("carl.hashCode(): " + carl.hashCode());
}
輸出:
alice1.hashCode(): -808853550
alice3.hashCode(): -808853550
bob.hashCode(): -624019882
carl.hashCode(): -2004699436

 

   3.toString方法

  Object中的toString方法用於返回表示對象值的字符串。

  例如:

  Employee中的toString方法:

   public String toString()
   {
      return getClass().getName() + "[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay
            + "]";
   }

  子類Manager中的toString方法:

   public String toString()
   {
      return super.toString() + "[bonus=" + bonus + "]";
   }

  測試和輸出:

   public static void main(String[] args)
   {

      Employee bob = new Employee("Bob Brandson", 50000, 1989, 10, 1);
      System.out.println("bob.toString(): " + bob);
      Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      boss.setBonus(5000);
      System.out.println("boss.toString(): " + boss);

   }
}
輸出:
bob.toString(): aaaaTest.Employee[name=Bob Brandson,salary=50000.0,hireDay=1989-10-01]
boss.toString(): aaaaTest.Manager[name=Carl Cracker,salary=80000.0,hireDay=1987-12-15][bonus=5000.0]


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM