在Java中,子類別只能繼承一個父類別,如果定義類別時沒有使用extends關鍵字指定繼承任何類別,那一定是繼承java.lang.Object,也就是說,如果你如下定義類別:
public class Some {
...
}
那就相當於如下撰寫:
public class Some extends Object {
...
}
因此在Java中,任何類別追溯至最上層父類別,一定就是java.lang.Object,也就是Java中所有物件,一定「是一種」Object,所以如下撰寫程式是合法的:
Object o1 = "Justin";
Object o2 = new Date();
String是一種Object,Date是一種Object,任何型態的物件,都可以使用Object宣告的名稱來參考。這有什麼好處?如果有個需求是使用陣列收集各種物件,那該宣告為什麼型態呢?答案是Object[]。例如:
Object[] objs = {"Monica", new Date(), new SwordsMan()};
String name = (String) objs[0];
Date date = (Date) objs[1];
SwordsMan swordsMan = (SwordsMan) objs[2];
因為陣列長度有限,使用陣列來收集物件不是那麼地方便,以下定義的ArrayList類別,則可以不限長度地收集物件:
package cc.openhome;
import java.util.Arrays;
public class ArrayList {
private Object[] list;
private int next;
public ArrayList(int capacity) {
list = new Object[capacity];
}
public ArrayList() {
this(16);
}
public void add(Object o) {
if(next == list.length) {
list = Arrays.copyOf(list, list.length * 2);
}
list[next++] = o;
}
public Object get(int index) {
return list[index];
}
public int size() {
return next;
}
}
自定義的ArrayList類別,內部使用Object陣列來收集物件,每一次收集的物件會放在next指定的索引處,在建構ArrayList實例時,可以指定內部陣列初始容量,如果使用無參數建構式,則預設容量為16。
如果要收集物件,可透過add()方法,注意參數的型態為Object,可以接收任何物件,如果內部陣列原長度不夠,就使用Arrays.copyOf()方法自動建立原長度兩倍的陣列並複製元素,如果想取得收集之物件,可以使用get()指定索引取得,如果想知道已收集的物件個數,則透過size()方法得知。
以下使用自定義的ArrayList類別,可收集訪客名稱,並將名單轉為大寫後顯示:
package cc.openhome;
import java.util.Scanner;
import static java.lang.System.out;
public class Guest {
public static void main(String[] args) {
ArrayList names = new ArrayList();
collectNameTo(names);
out.println("訪客名單:");
printUpperCase(names);
}
static void collectNameTo(ArrayList names) {
Scanner scanner = new Scanner(System.in);
String name;
while(true) {
out.print("訪客名稱:");
name = scanner.nextLine();
if(name.equals("quit")) {
break;
}
names.add(name);
}
}
static void printUpperCase(ArrayList names) {
for(int i = 0; i < names.size(); i++) {
String name = (String) names.get(i);
out.println(name.toUpperCase());
}
}
}
一個執行結果如下所示:
訪客名稱:Monica
訪客名稱:Irene
訪客名稱:quit
訪客名單:
JUSTIN
MONICA
IRENE
java.lang.Object是所有類別的頂層父類別,這代表了Object上定義的方法,所有物件都繼承下來了,只要不是被定義為final的方法,都可以重新定義。重新定義toString()
舉例來說,在
protected 成員 的範例中,SwordsMan等類別曾定義過toString()方法,其實toString()是Object上定義的方法,Object的toString()預設定義為:public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode());}目前你不用特別知道這段程式碼詳細內容,總之傳回的字串包括了類別名稱以及16進位雜湊碼,通常這並沒有什麼閱讀上的意義。實際上
protected 成員 的範例中,SwordsMan等類別,是重新定義了toString(),許多方法若傳入物件,預設都會呼叫toString(),例如System.out.print()等方法就會呼叫toString()以取得字串描述來顯示,所以 protected 成員 的這個程式片段:SwordsMan swordsMan = new SwordsMan();...略System.out.println(swordsMan.toString());Magician magician = new Magician();...略System.out.printf(magician.toString());實際上只要這麼撰寫就可以了:
SwordsMan swordsMan = new SwordsMan();...略System.out.println(swordsMan);Magician magician = new Magician();...略System.out.printf(magician);重新定義equals()
==,而是透過equals()方法,你看過Integer等包裹器,以及字串相等性比較時,都是使用equals()方法。實際上
equals()方法是Object類別就有定義的方法,其程式碼實作是:public boolean equals(Object obj) { return (this == obj);}如果沒有重新定義
equals(),使用equals()方法時,作用等同於==,所以要比較實質相等性,必須自行重新定義。一個簡單的例子是比較,兩個Cat物件是否實際上代表同一隻Cat的資料:public class Cat { ... public boolean equals(Object other) { // other參考的就是這個物件,當然是同一物件 if (this == other) { return true; } /* other參考的物件是不是Cat建構出來的 例如若是Dog建構出來的當然就不用比了 */ if (other instanceof Cat) { Cat cat = (Cat) other; // 定義如果名稱與生日,表示兩個物件實質上相等 return getName().equals(cat.getName()) && getBirthday().equals(cat.getBirthday()); } return false; }}這個程式片段示範了
equals()實作的基本概念,相關說明都以註解方式呈現了,這邊也看到了instanceof運算子,它可以用來判斷物件是否由某個類別建構,左運算元是物件,右運算元是類別,在使用instanceof時,編譯器還會來幫點忙,會檢查左運算元型態是否在右運算元型態的繼承架構中(或介面實作架構中,之後會說明介面)。例如:執行時期,並非只有左運算元物件為右運算元類別直接實例化才傳回
true,只要左運算元型態是右運算元型態的子類型,instanceof也是傳回true。不過,這邊的
equals()並不安全,如果getName()或getBirthday()傳回null的話,那麼就會噴出NullPointerException了,自行加些檢查是否為null的程式碼是可以,不過知道有Objects.equals()可以協助(除了equals()外,Objects上還有一些不錯用的方法,請參考API文件),為什麼不拿來用?import static java.util.Objects.equals;public class Cat { ... public boolean equals(Object other) { // other參考的就是這個物件,當然是同一物件 if (this == other) { return true; } /* other參考的物件是不是Cat建構出來的 例如若是Dog建構出來的當然就不用比了 */ if (other instanceof Cat) { Cat cat = (Cat) other; // 定義如果名稱與生日,表示兩個物件實質上相等 return equals(getName(), cat.getName()) && equals(getBirthday(), cat.getBirthday()); } return false; }}來看一下
Objects的equals()原始碼,比較能安心使用: public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}這邊僅示範了
equals()實作的基本概念,實際上實作equals()並非這麼簡單,實作equals()時通常也會實作hashCode(),原因會等到學習Collection時再說明,如果現在你就想知道equals()與hashCode()實作時要注意的一些事項,可以先參考 物件相等性。2007年研究文獻"Declarative Object Identity Using Relation Types"中指出,在考察大量Java程式碼之後,作者發現幾乎所有
equals()方法都實作錯誤。

