DEV Community

codemee
codemee

Posted on

談 Java Object 類別中 3 個讓你覆寫的方法

大家都知道 Java 中整個繼承體系的最上方就是 Object 類別, 它也是定義類別沒有指明繼承對象時預設的父類別, 因此 Object 提供了幾個讓你覆寫的方法, 以便能搭配 Java 的其他機制妥善運作, 本文就來看這幾個特別的方法。

toString()

toString() 是在你的類別建立的物件要轉換成字串時會被自動叫用的方法, 不過 Object 類別不可能未卜先知, 知道你的類別要轉換成什麼樣的文字內容, 因此預設的實作是轉成 "類別名稱@物件的識別碼雜湊值" 這樣的格式:

jshell> class A {}
|  created class A

jshell> A a1 = new A()
a1 ==> A@5e8c92f4

jshell> A a2 = new A()
a2 ==> A@50134894

jshell> a1.toString()
$48 ==> "A@5e8c92f4"

jshell> a2.toString()
$49 ==> "A@50134894"
Enter fullscreen mode Exit fullscreen mode

@ 後面的 16 進位數字就是這個物件的識別碼雜湊值, 每一個物件都會擁有一個自己專屬的識別碼雜湊值, 要識別兩個物件是否為同一個物件, 可以看他們的識別碼雜湊值是否相同。你也可以透過 System.identityHashCode() 取得物件的識別碼雜湊值:

jshell> System.identityHashCode(a1)
$44 ==> 1586270964

jshell> Integer.toHexString(System.identityHashCode(a1))
$45 ==> "5e8c92f4"

jshell> Integer.toHexString(System.identityHashCode(a2))
$46 ==> "50134894"
Enter fullscreen mode Exit fullscreen mode

你可以看到跟剛剛建立物件時看到的識別碼雜測值是一樣的。

你可以透過覆寫 toString() 方法轉換成適當的字串內容:

jshell> class A {
   ...>     public String toString() {
   ...>         return "this is class A.";
   ...>     }
   ...> }
|  replaced class A
|    update replaced variable a1, reset to null

jshell> A a1 = new A()
a1 ==> this is class A.

jshell> a1.toString()
$10 ==> "this is class A."

jshell> a1
a1 ==> this is class A.

jshell> System.out.println(a1)
this is class A.
Enter fullscreen mode Exit fullscreen mode

你可以看到不論是明確叫用 toString() 或是讓 jshell 自動轉換, 甚或是傳入 System.out.println(), 都會透過覆寫的方法轉換成指定內容的文字。對於自訂的類別, 請記得要覆寫 toString() 方法。

equals()

equals() 是用來判斷兩個物件的內容是否相等, 這和 == 不一樣, == 是判斷兩個物件是否為同一個, 也可視為判斷兩個物件的識別碼雜湊值是否相同, 例如:

jshell> class C {}
|  created class C

jshell> C c1 = new C()
c1 ==> C@51521cc1

jshell> C c2 = new C()
c2 ==> C@694f9431

jshell> C c3 = c1
c3 ==> C@51521cc1

jshell> c1 == c2
$35 ==> false

jshell> c1 == c3
$36 ==> true
Enter fullscreen mode Exit fullscreen mode

你可以看到利用 new 建立的不同物件就會擁有不同的識別碼雜湊值。

不過 Object 並不知道怎麼比較我們自訂的類別所產生的物件, 因此預設的實作就跟 == 一樣, 如果你看 String 類別, 就會看到兩者的差別:

jshell> String s1 = "hello"
s1 ==> "hello"

jshell> String s2 = "hello"
s2 ==> "hello"

jshell> String s3 = new String("hello")
s3 ==> "hello"

jshell> System.identityHashCode(s1)
$22 ==> 764308918

jshell> System.identityHashCode(s2)
$23 ==> 764308918

jshell> System.identityHashCode(s3)
$24 ==> 580024961

jshell> s1 == s2
$39 ==> true

jshell> s1.equals(s2)
$40 ==> true

jshell> s1 == s3
$41 ==> false

jshell> s1.equals(s3)
$42 ==> true
Enter fullscreen mode Exit fullscreen mode

你可以看到只要字串內容相同, 不論是不是同一個物件, equals() 都會傳回 true, 但是 == 是比較他們是否為同一物件。

對於你自己的類別, 就應該要覆寫 equals(), 客製化比較內容是否相等的方法, 請看以下的例子:

shell> class D { public int num;}
|  created class D

jshell> D d1 = new D()
d1 ==> D@6d5380c2

jshell> d1.num = 20
$3 ==> 20

jshell> D d2 = new D()
d2 ==> D@bebdb06

jshell> d2.num = 20
$5 ==> 20

jshell> d1 == d2
$6 ==> false

jshell> d1.equals(d2)
$7 ==> false
Enter fullscreen mode Exit fullscreen mode

由於沒有覆寫 equals(), 會以預設的實作執行, 因此雖然兩個物件的內容相同, 但因為是不同的物件, 還是會被判訂為不相等。以下是覆寫 equals() 的版本:

jshell> class D {
   ...>     public int num;
   ...>     public boolean equals(D obj) {
   ...>         return this.num == obj.num;
   ...>     }
   ...> }
|  replaced class D
|    update replaced variable d2, reset to null
|    update replaced variable d1, reset to null

jshell> D d1 = new D()
d1 ==> D@2d38eb89

jshell> d1.num = 20
$12 ==> 20

jshell> D d2 = new D()
d2 ==> D@4629104a

jshell> d2.num = 20
$14 ==> 20

jshell> d1 == d2
$15 ==> false

jshell> d1.equals(d2)
$16 ==> true
Enter fullscreen mode Exit fullscreen mode

雖然 d1d2 是不同的物件, 但因為內容相同, 就會判定為相等了。

hashCode()

hashCode 這個方法是為了搭配 Java 的 HashMap 這類需要以雜湊值 (hash value) 當索引鍵存取資料的結構使用。不過 Object 當然也無法預先知道你的類別建立的物件要如何計算雜湊值, 因此預設的實作就是傳回物件自己的識別碼雜湊值, 例如:

jshell> class B {}
|  created class B

jshell> B b1 = new B()
b1 ==> B@573fd745

jshell> b1.hashCode()
$17 ==> 1463801669

jshell> Integer.toHexString(b1.hashCode())
$18 ==> "573fd745"
Enter fullscreen mode Exit fullscreen mode

在自訂的類別中應該要覆寫這個方法, 由於這個方法的傳回值要當成索引鍵, 所以 Java 有明文規定 hashCode() 必須符合以下條件:

  1. 同一物件不論叫用多少次 hashCode(), 都應該傳回相同的值。
  2. 不同的物件如果叫用 equals() 得到 true, 那他們的 toHash() 就應該傳回相同的值。
  3. 兩個物件如果叫用 equals() 得到 false, 他們的 hashCode() 並不一定要傳回不同的值。

String 類別來說, 只要字串內容相同, 不論是不是同一個物件, hashCode() 就會傳回相同的值。以前一小節的 s1~s3 這 3 個字串為例:

jshell> s1.hashCode()
$25 ==> 99162322

jshell> s2.hashCode()
$26 ==> 99162322

jshell> s3.hashCode()
$27 ==> 99162322
Enter fullscreen mode Exit fullscreen mode

由於 3 個字串內容相等, 所以取得的雜湊值都一樣。

Object 類別定義的 hashCode() 因為會傳回識別碼雜湊值, 會讓相同內容的不同物件產生不同的雜湊碼, 所以並不符合前述對於 hashCode() 的要求, 因此在定義類別時, 如果該類別的物件會用在需要雜湊值的情境下, 就應該要覆寫 hashCode(), 例如:

jshell> class E {
   ...>     public int num;
   ...>     public int hashCode() {
   ...>         return Objects.hash(this.num);
   ...>     }
   ...> }
|  created class E

jshell> E e1 = new E()
e1 ==> E@1f

jshell> e1.num = 20
$26 ==> 20

jshell> E e2 = new E()
e2 ==> E@1f

jshell> e2.num = 20
$28 ==> 20

jshell> E e3 = new E()
e3 ==> E@1f

jshell> e3.num = 40
$30 ==> 40

jshell> e1.hashCode()
$31 ==> 51

jshell> e2.hashCode()
$32 ==> 51

jshell> e3.hashCode()
$33 ==> 71
Enter fullscreen mode Exit fullscreen mode

我們直接採用 Objectshash() 類別方法幫我們計算雜湊值, 你可以看到現在 e1e2 會產生相同的雜湊碼, 但 e3 因為其 num 值不同, 所以產生的雜湊碼就不相同。

小結

透過以上簡單的說明, 你應該已經瞭解在定義類別時, 依照需求覆寫特定的方法, 搭配運用情境正確運作了。

Oldest comments (0)