From Gossip@Openhome

Java Gossip: Object 類別

在Java中,所有的物件都隱含的擴充了Object類別,Object類別是Java程式中所有類別的父類別,當您定義一個類別時:
public class Foo {
    // 實作
}

這個程式碼相當於:
public class Foo extends Object {
    // 實作
}

Object類別定義了幾個方法,包括"protected"的clone()、finalize()兩個方法,以及幾個"public"方法,像是equals()toString()getClass()hashCode()notify()notifyAll()等等的方法,這些方法您都可以加以重新定義(除了 getClass()、notify()、notifyAll()、wait()等方法之外,它們被宣告為 "final",無法被子類別重新定義),以符合您所建立的類別需求,我會在以後的適當主題中介紹這些方法的使用。

由於Object類別是Java中所有類別的父類別,所以它可以參考至任何的物件而不會發生任何錯誤,這是很有用,以後您會看到一些 Java程式中,有些物件可以加入一些衍生類別物件,並可透過方法呼叫會直接傳回Object物件,這些物件可以經由型態(介面)轉換而指定給衍生類別型 態參考。

下面這個程式中,您製作一個簡單的 集合(Collection)類別,並將一些自訂類別物件加入其中,這個程式示範了Object的一個應用:

  • Foo1.java
public class Foo1 { 
private String name;

public Foo1(String name) {
this.name = name;
}

public void showName() {
System.out.println("foo1 name: " + name);
}

// 重新定義toString()
public String toString() {
return "foo1 name: " + name;
}
}

  • Foo2.java
public class Foo2 { 
private String name;

public Foo2(String name) {
this.name = name;
}

public void showName() {
System.out.println("foo2 name: " + name);
}

// 重新定義toString()
public String toString() {
return "foo2 name: " + name;
}
}
  • SimpleCollection.java
public class SimpleCollection { 
private Object[] objArr;
private int index = 0;

public SimpleCollection() {
objArr = new Object[10]; // 預設10個物件空間
}

public SimpleCollection(int capacity) {
objArr = new Object[capacity];
}

public void add(Object o) {
objArr[index] = o;
index++;
}

public int getLength() {
return index;
}

public Object get(int i) {
return objArr[i];
}
}
  • Test.java
public class Test { 
public static void main(String[] args) {
SimpleCollection objs = new SimpleCollection();

objs.add(new Foo1("f1 number 1"));
objs.add(new Foo2("f2 number 1"));

Foo1 f1 = (Foo1) objs.get(0);
f1.showName();

Foo2 f2 = (Foo2) objs.get(1);
f2.showName();

System.out.println();
System.out.println("f1.toString(): " +
f1.toString());
System.out.println("f2.toString(): " +
f2.toString());
}
}

執行結果:
foo1 name: f1 number 1
foo2 name: f2 number 1
 
f1.toString(): foo1 name: f1 number 1
f2.toString(): foo2 name: f2 number 1 


在程式中,SimpleCollection物件可以加入任何型態的物件至其中,而傳回物件時,您只要透過型態(介面)轉換,就可以操作型態(介面)上的方法。

Object的toString()方法預設會傳回以下的字串:
getClass().getName() + '@' +
              Integer.toHexString(hashCode());

getClass()方法是Object中定義的方法,它會傳回物件於執行時期的Class實例,而hashCode()傳回該物件的hash code,toString()方法用來傳回物件的描述,通常是個文字性的描述,Object的toString()方法預設在某些場合是有用的,例如物 件的自我檢視時,但在這邊,您將之重新定義為文字模式下使用者看得懂的文字描述。

上面這個程式範例雖然簡單,但您以後一定會常常看到類似的應用,例如視窗程式容器、Vector類別等等。

Object預設的equals()本身是比較物件的記憶體參考,如果您要有必要比較兩個物件的內含資料是否相同(例如當物件被儲存至Set時)您必須實作equals()與hashCode()

一個比較常被採用的方法是根據物件中包括的所有屬性值來作比較,來看看下面的一個例子:
public class Cat {

    ...
    public boolean equals(Object other) {
        if (this == other)
            return true;

        if (!(other instanceof Cat))
            return false;

        final Cat cat = (Cat) other;

        if (!getName().equals(cat.getName()))
            return false;

        if (!getBirthday().equals(cat.getBirthday()))
            return false;

        return true;
    }

    public int hashCode() {
        int result;
        result = getName().hashCode();
        result = 29 * result + getBirthday().hashCode();
        return result;
    }
}

這稱之為by value equality,更嚴格的比對是根據商務鍵值(Business key)實作equals()與hashCode(),
商務鍵是一個屬性或多個屬性的結合,挑選那些可以從不為null、很少改變且組合後具有唯一性的屬性

API中對於equals()的合約是必須具備反身性(Reflexive)、對稱性(Symmetric)、傳遞性(Transitive)、一致性(Consistent)
  • 反身性(Reflexive):x.equals(x)的結果要是true。
  • 對稱性(Symmetric):x.equals(y)與y.equals(x)的結果必須相同。
  • 傳遞性(Transitive):x.equals(y)、y.equals(z)的結果都是true,則x.equals(z)的結果也必須是true。
  • 一致性(Consistent):同一個執行期間,對x.equals(y)的多次呼叫,結果必須相同。

可以參考API文件中Object類別的hashCode()之建議:
  • 在同一個應用程式執行期間,對同一物件呼叫 hashCode()方法,必須回傳相同的整數結果。
  • 如果兩個物件使用equals(Object)測試結果為相等, 則這兩個物件呼叫hashCode()時,必須獲得相同的整數結果。
  • 如果兩個物件使用equals(Object)測試結果為不相等, 則這兩個物件呼叫hashCode()時,可以獲得不同的整數結果。

兩個不同的物件,可以傳回相同的hashCode()結果,這是合法甚至適當的,只是物件會被丟到同一個雜湊桶中。

至於clone()方法,它是有關於如何複製物件本身,您可以在當中定義您的複製方法,不過物件的複製要深入的話必須考慮很多細節,您可以從 這篇文章 開始稍微瞭解一下如何定義clone()方法。