大家都知道 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"
在 @
後面的 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"
你可以看到跟剛剛建立物件時看到的識別碼雜測值是一樣的。
你可以透過覆寫 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.
你可以看到不論是明確叫用 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
你可以看到利用 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
你可以看到只要字串內容相同, 不論是不是同一個物件, 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
由於沒有覆寫 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
雖然 d1
和 d2
是不同的物件, 但因為內容相同, 就會判定為相等了。
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"
在自訂的類別中應該要覆寫這個方法, 由於這個方法的傳回值要當成索引鍵, 所以 Java 有明文規定 hashCode()
必須符合以下條件:
- 同一物件不論叫用多少次
hashCode()
, 都應該傳回相同的值。 - 不同的物件如果叫用
equals()
得到true
, 那他們的toHash()
就應該傳回相同的值。 - 兩個物件如果叫用
equals()
得到false
, 他們的hashCode()
並不一定要傳回不同的值。
以 String
類別來說, 只要字串內容相同, 不論是不是同一個物件, hashCode()
就會傳回相同的值。以前一小節的 s1
~s3
這 3 個字串為例:
jshell> s1.hashCode()
$25 ==> 99162322
jshell> s2.hashCode()
$26 ==> 99162322
jshell> s3.hashCode()
$27 ==> 99162322
由於 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
我們直接採用 Objects
的 hash()
類別方法幫我們計算雜湊值, 你可以看到現在 e1
和 e2
會產生相同的雜湊碼, 但 e3
因為其 num
值不同, 所以產生的雜湊碼就不相同。
小結
透過以上簡單的說明, 你應該已經瞭解在定義類別時, 依照需求覆寫特定的方法, 搭配運用情境正確運作了。
Top comments (0)