在 Java 中,所有的物件都隱含的擴充了 java.lang.Object 類別,Object 類別是 Java 程式中所有類別的父類別,當您定義一個類別時:
public class Foo { // 實作 }
這個程式碼相當於:
public class Foo extends Object { // 實作 }
圖11-16 Object 是 Java 中所有類別之父類別
Object 中定義了許多個方法,包括公開的(public) equals()、toString() 、 hashCode() 、 getClass() 、 wait() 、 notify() 、notifyAll(),保護的(protected)clone()、finalize(),這些方法中 getClass()、notify()、notifyAll()、wait()被宣告為"final",所以您無法重新定義這些方法,而其它的方法您都可以重新定義。
clone()、getClass()、notify()、notifyAll()、wait()的使用是進階課題,不在本單元中介紹,以下則將介紹 toString()、finalize()、equals()、hashCode()的使用。
Object 的 toString()方法目的是傳回物件本身的描述,預設上Object 的 toString()方法會傳回以下的內容:
public String toString() { return getClass().getName() + '@' + Integer.toHexString(hashCode()); }
getClass()方法會取得類別於 JVM 中的 Class 實例,呼叫該實例的 getName()將傳回類別名稱;hashCode()則是傳回實例的「雜湊碼」(Hash code)。
註
「雜湊碼」(Hash code)由雜湊函式計算得到,在資料結構上可用於資料的定址,在 Java 中即使您不是很了解其原理與作用,仍可以直接使用相關類別來得到相同的好處,您可以參考資料結構專書了解雜湊碼的原理與作用。
在 Java SE 中,每個類別都會適當的重新定義 toString()方法,以傳回相關的物件描述,直接來看幾個例子,程式碼 11-15 將直接呼叫各個物件的 toString()方法來顯示物件的描述。
public class ToStringDemo { public static void main(String[] args) { Object obj = new Object(); int[] array1 = {1, 2, 3, 4, 5}; double[] array2 = {1.0, 2.0, 3.0}; String str = "Java"; System.out.println(obj.toString()); System.out.println(array1.toString()); System.out.println(array2.toString()); System.out.println(str.toString()); } }程式碼 11-15 ToStringDemo.java
在 Java 中,陣列是以物件的方式存在,而且在 JVM 中會有一個代表它的類別實例存在,名稱為[開始,例如[I 表示 int[] 型態陣列物件,[D 表示 double[]陣列物件,因而程式碼 11-15 顯示的會是像[I@757aef 與[D@d9f9c3 的字樣,而 String 類別的 toString()方法,傳回的是本身的字串內容。
事實上 System.out 的 println()等方法如果直接給它一個物件作為引數,則會自動呼叫該物件的 toString()方法,所以程式碼中的第 8 行到第 11 行,也可以直接如下撰寫:
System.out.println(obj); System.out.println(array1); System.out.println(array2); System.out.println(str);
您也可以在繼承了某類別時,重新定義 toString()方法,為該類別加上適當的字串描述,例如在程式碼 11-13 中重新定義toString()方法:
public class Pants extends Clothes { private int waistline; ... public String toString() { return size + "," + waistline + "," + price; } }
在 Java 中建立的物件,如果沒有被任何的名稱參考,它將會被 JVM 回收,以釋放物件所佔據的資源,例如:
在圖 11-7 中,object1、object2、object4 三個物件有被名稱參考,而 object3 沒有被任何名稱參考,也就是不會有方式可以使用到 object3,JVM 發現到這樣一個物件時,會在適當的時候回收該物件。
在 JVM 回收物件之前,會執行物件的 finalize()方法,您可以在這個方法中撰寫一些物件被回收前的善後工作,然而要注意的是,JVM 何時會回收物件並無法預知,所以如果您有立即性必須馬上處理的工作,不可以依賴在 finalize()方法中完成,當您讓某個物件不再被參考,JVM 回收它的時間是在一分鐘、五分鐘或十分鐘之後回收物件並不可得知,所以您只可以在 finalize()中撰寫一些非即時必須處理的善後工作,例如留下操作簡單的記錄(log)之類,而不能放一些像是釋放資料庫連結的動作。
在物件導向程式中,物件往往在程式的各個角落被參考,何時該回收資源是件複雜且難以判斷的工作,自動回收資源是Java 的垃圾收集( Garbage collection )機制,為的是讓開發人員不用費心於物件何時該釋放資源,必要的時候您可以建議 JVM 進行物件的回收,但也只能建議,因為 JVM 並不一定採納您的建議,當程式中有更高優先權的執行緒(Thread)在執行時,您的建議會被忽略。
程式碼 11-16 示範了使用 finalize()方法,並在 main 中讓建立的物件不再被參考,以測試 finalize()方法是否被執行:
public class Some { private String name; public Some(String name) { this.name = name; System.out.println(name + ": 被建立"); } protected void finalize() { System.out.println(name + ": 被回收"); } public static void main(String[] args) { Some some1 = new Some("Object 1"); Some some3 = new Some("Object 3"); Some some2 = new Some("Object 2"); some1 = null; some2 = null; some3 = null; // 建議回收資源 System.gc(); System.out.println("程式結束"); } }程式碼 11-16 Some.java
程式中建立了三個 Some 類別的實例,並分別給予不同的名稱,之後在第 17 行至第 19 行讓 some1、some2、some3 分別參考至 null,而原來它們所參考的物件就不再被任何名稱參考,此時建議 JVM 回收物件,如果 JVM 採納建議,就會有以下的執行結果:
Object 的 equals()方法預設是比較物件的記憶體參考是否相同,開啟 Object 類別的原始碼,您也發現它的 equals()只是使用==運算子來比較:
public boolean equals(Object obj) { return (this == obj); }
在定義類別之時,通常建議重新定義 equals()方法,以定義您的物件之相等性,例如 String 類別就重新定義了 equals() 方法,在比較兩個 String 實例時,如果 String 物件的內含字元值都相同,視為兩個物件相等。
如果您要定義物件的相等性,在定義 equals()方法時,通常建議一併重新定義 hashCode()方法,程式碼 11-17 示範了如何定義一個 Student,並重新定義 equals()與 hashCode():
public class Student { private String name; private int number; public Student() { } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setNumber(int number) { this.number = number; } public int getNumber() { return number; } // 重新定義 equals() public boolean equals(Object other) { if (this == other) return true; if (!(other instanceof Student)) return false; final Student student = (Student) other; if (!getName().equals(student.getName())) return false; if (number != student.getNumber()) return false; return true; } // 重新定義 hashCode() public int hashCode() { int result = getName().hashCode(); result = result + number; return result; } }程式碼 11-17 Student.java
程式中的第 21 行首先使用==運算子測試兩個物件是否為同一物件,如果是的話直接返回 true 而不用再測試是否相等,接下來第 23 行使用 instanceof 運算子測試兩個物件是否為同一個類別的實例,如果不是就直接返回 false 而無須再往下進行比較,接著第 26 行到第 30 行都是在比較物件的資料成員值是否相同,這是很常見的比較物件相等性的方法,如果資料成員值有一個不同就返回 false,在所在的測試都通過的話,第 32 行返回 true。
在 hashCode()方法的設計上,這邊僅簡單的取得 name 屬性的雜碼碼,並加上 number 屬性的值,這只是個簡單的示範,hashCode實際上在設計時得依需求來決定如何製作 hash 碼,可以參考 API 文件中 Object 類別對 hashCode()之建議: