如果你需要持有一組物件,在Java中,你應該會選擇使用Collection相關類別,例如:
List list = new ArrayList();
list.add("字串物件一");
list.add("字串物件二");
list.add("字串物件一");
list.add("字串物件二");
Collection或Map的實作類別,允許你持有不同類型的物件,不過絕大多數的情況下,你所需持有的物件是同一種物件。Collection或Map是使用Object型態來持有物件,如果你需要取回物件,你必須記得當初放入的物件類型,並且:
String str = (String) list.get(0);
這是在JDK1.4之前的情況,如果你搞錯型態,將會發生ClassCastException。
當你在JDK5之後的版本下編譯以上的程式碼,將會出現警示訊息:
Note: Test.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Note: Recompile with -Xlint:unchecked for details.
遵照指示,重新加上-Xlint:unchecked進行編譯,你會看到:
Test.java:5: warning: [unchecked] unchecked call to add(E) as a member of the ra
w type java.util.List
list.add("字串物件一");
^
Test.java:6: warning: [unchecked] unchecked call to add(E) as a member of the ra
w type java.util.List
list.add("字串物件二");
^
2 warnings
w type java.util.List
list.add("字串物件一");
^
Test.java:6: warning: [unchecked] unchecked call to add(E) as a member of the ra
w type java.util.List
list.add("字串物件二");
^
2 warnings
警示訊息不是錯!編譯器在告訴你,你有個操作可能不安全,將來可能發生ClassCastException。它有更好的功能,也許你可以試試。例如:
List<String> list = new ArrayList<String>();
list.add("字串物件一");
list.add("字串物件二");
String str = list.get(0);
list.add("字串物件一");
list.add("字串物件二");
String str = list.get(0);
這次加上了角括號,並在角括號中指定將持有之物件類型,當從List中取回物件時,也不再需要轉型,將這個程式片段編譯,編譯器不再出現警示訊息,這是JDK5之後新增的Generic功能。
實際上,如果你反組譯程式,會發現這又是編譯器提供的蜜糖功能之一。
ArrayList arraylist = new ArrayList();
arraylist.add("\u5B57\u4E32\u7269\u4EF6\u4E00");
arraylist.add("\u5B57\u4E32\u7269\u4EF6\u4E8C");
String s = (String)arraylist.get(0);
arraylist.add("\u5B57\u4E32\u7269\u4EF6\u4E00");
arraylist.add("\u5B57\u4E32\u7269\u4EF6\u4E8C");
String s = (String)arraylist.get(0);
跟你原來沒有Generic時所作的一樣。在JDK5之後,Collection與Map所有實作類別,都以Generic改寫過了。正如先前所說的,其實絕大多數的情況下,你所持有的物件都會是同一類型,藉由Generic語法,以及編譯器的展開,可以在編譯時期就作好型態檢查動作,避免ClassCastException的發生。
你可以自行定義Generic類別,例如:
public class Some<T> {
private T thing;
public void setThing(T thing) {
this.thing = thing;
}
public T getThing() {
return thing;
}
}
並且這麼使用它:
Some<String> some = new Some<String>();
some.setThing("some thing");
String something = some.getThing();
some.setThing("some thing");
String something = some.getThing();
實際上,當你反組譯Some.class時,會發現:
public class Some
{
public Some()
{
}
public void setThing(Object obj)
{
thing = obj;
}
public Object getThing()
{
return thing;
}
private Object thing;
}
{
public Some()
{
}
public void setThing(Object obj)
{
thing = obj;
}
public Object getThing()
{
return thing;
}
private Object thing;
}
在JDK1.4之前,若想進行Generic的類別定義,正是使用Object來持有型態,並且再取回之後進行轉型的動作,將物件轉為正確的型態。反組譯使用Some的那段程式碼,發現也是這麼作的:
Some some = new Some();
some.setThing("some thing");
String s = (String)some.getThing();
some.setThing("some thing");
String s = (String)some.getThing();
JDK5之後的Generic語法,還可以限制可指定之類型,例如:
import java.io.*;
public class Some<T extends Serializable> {
private T thing;
public void setThing(T thing) {
this.thing = thing;
}
public T getThing() {
return thing;
}
}
如果你反組譯這段程式碼,會發現其中的神秘:
import java.io.Serializable;
public class Some
{
public Some()
{
}
public void setThing(Serializable serializable)
{
thing = serializable;
}
public Serializable getThing()
{
return thing;
}
private Serializable thing;
}
public class Some
{
public Some()
{
}
public void setThing(Serializable serializable)
{
thing = serializable;
}
public Serializable getThing()
{
return thing;
}
private Serializable thing;
}
當 然!編譯器提供的語法展開等功能,讓你可以直接使用角括號來撰寫Generic語法,實際上編譯出的.class檔案,還會有額外的Generic資訊, 這些資訊讓使用該類別的程式片段在編譯時,可為編譯器所用。因此,若程式使用了Generic資訊,你的.class將無法使用於JDK1.4而更之前的 版本。
如果你真的要在Collection等中放入不同型態的物件呢?
public void do() {
List list = new ArrayList();
list.add("Some");
list.add(new Date());
...
}
List list = new ArrayList();
list.add("Some");
list.add(new Date());
...
}
如果不想編譯器出現惱人的警示訊息,則加上個@SuppressWarnings可以解決問題:
@SuppressWarnings(value={"unchecked"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("Some");
list.add(new Date());
}