例如,類ArrayList就是被設計者設計成泛型,它帶有一個泛型類型
public class ArrayListimplements List .... { // Constructor public ArraList() { ...... } // Public methods public boolean add(E e) { ...... } public void add(int index, E element) { ...... } public boolean addAll(int index, Collection c) public abstract E get(int index) { ...... } public E remove(int index) ....... }
ArrayListlst1 = new ArrayList (); // E substituted with Integer lst1.add(0, new Integer(88)); lst1.get(0); ArrayList lst2 = new ArrayList (); // E substituted with String lst2.add(0, "Hello"); lst2.get(0);
// Pre-JDK 1.5 import java.util.*; public class ArrayListWithoutGenericsTest { public static void main(String[] args) { List strLst = new ArrayList(); // List and ArrayList holds Objects strLst.add("alpha"); // String upcast to Object implicitly strLst.add("beta"); strLst.add("charlie"); Iterator iter = strLst.iterator(); while (iter.hasNext()) { String str = (String)iter.next(); // need to explicitly downcast Object back to String System.out.println(str); } strLst.add(new Integer(1234)); // Compiler/runtime cannot detect this error String str = (String)strLst.get(3); // compile ok, but runtime ClassCastException } }為了解決這個問題,也就是在編譯的時候就可以進行類型的檢查,這樣就引入了泛型。
2. 泛型
下面是一個自定義的ArrayList版本,叫做MyArrayList,它沒有使用泛型。
// A dynamically allocated array which holds a collection of java.lang.Object - without generics public class MyArrayList { private int size; // number of elements private Object[] elements; public MyArrayList() { // constructor elements = new Object[10]; // allocate initial capacity of 10 size = 0; } public void add(Object o) { if (size < elements.length) { elements[size] = o; } else { // allocate a larger array and add the element, omitted } ++size; } public Object get(int index) { if (index >= size) throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); return elements[index]; } public int size() { return size; } }從上面很輕易的就可以看出MyArrayList不是類型安全的,例如,如果我們希望創建一個存放String類型對象的MyArrayList,但是我們向裡面添加了一個Integer對象,編譯器是不能檢查出異常,這是因為我們MyArrayList設計的是存放Object類型的元素,並且任何對象類型都可以向上轉換為Object類型的對象。
public class MyArrayListTest { public static void main(String[] args) { // Intends to hold a list of Strings, but not type-safe MyArrayList strLst = new MyArrayList(); // adding String elements - implicitly upcast to Object strLst.add("alpha"); strLst.add("beta"); // retrieving - need to explicitly downcast back to String for (int i = 0; i < strLst.size(); ++i) { String str = (String)strLst.get(i); System.out.println(str); } // Inadvertently added a non-String object will cause a runtime // ClassCastException. Compiler unable to catch the error. strLst.add(new Integer(1234)); // compiler/runtime cannot detect this error for (int i = 0; i < strLst.size(); ++i) { String str = (String)strLst.get(i); // compile ok, runtime ClassCastException System.out.println(str); } } }
從上面可以看出,如果我們想創建一個String類型的List,但是我們添加了一個非String類型的對象元素,這個對象同樣是可以向上轉換為Object對象類型的,並且編譯器自動完成,編譯器並不能檢查它是否合法,這樣就存在一個隱患,當我們獲取這個元素的時候,它是一個Object類型,我們需要手動轉換為String類型,這個時候就會拋出ClassCastException異常,它發生在運行時期。
2.1 泛型類
JDK 1.5引入了所謂的泛型來解決這一問題,泛型允許我們去進行類型的抽象,我們可以創建一個泛型類並且在類實例化的時候指定具體類型信息。編譯器在編譯器期間會進行相應的類型檢查,這樣就確保了在運行時期不會有類型轉換的異常發生,這就是所謂的類型安全。
下面我們來看看java.util.List
public interface Listextends Collection { boolean add(E o); void add(int index, E element); boolean addAll(Collection c); boolean containsAll(Collection c); ...... }
例如:方法的定義,聲明形參
// A method's definition public static int max(int a, int b) { // int a, int b are formal parameters return (a > b) ? a : b; }方法的調用,傳遞實參
// Invocation: formal parameters substituted by actual parameters int maximum = max(55, 66); // 55 and 66 are actual parameters int a = 77, b = 88; maximum = max(a, b); // a and b are actual parameters返回到上面的java.util.List
正式的類型參數命名規范
一般使用一個大寫的字母作為類型參數。例如:
public class GenericBox{ // Private variable private E content; // Constructor public GenericBox(E content) { this.content = content; } public E getContent() { return content; } public void setContent(E content) { this.content = content; } public String toString() { return content + " (" + content.getClass() + ")"; } }
public class TestGenericBox { public static void main(String[] args) { GenericBoxbox1 = new GenericBox ("Hello"); String str = box1.getContent(); // no explicit downcasting needed System.out.println(box1); GenericBox box2 = new GenericBox (123); // autobox int to Integer int i = box2.getContent(); // downcast to Integer, auto-unbox to int System.out.println(box2); GenericBox box3 = new GenericBox (55.66); // autobox double to Double double d = box3.getContent(); // downcast to Double, auto-unbox to double System.out.println(box3); } }
Hello (class java.lang.String) 123 (class java.lang.Integer) 55.66 (class java.lang.Double)
下面我們返回到上面我們寫的MyArrayList的例子,我們知道它不是一個泛型類型,下面我們來寫一個泛型的版本。
// A dynamically allocated array with generics public class MyGenericArrayList{ private int size; // number of elements private Object[] elements; public MyGenericArrayList() { // constructor elements = new Object[10]; // allocate initial capacity of 10 size = 0; } public void add(E e) { if (size < elements.length) { elements[size] = e; } else { // allocate a larger array and add the element, omitted } ++size; } public E get(int index) { if (index >= size) throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); return (E)elements[index]; } public int size() { return size; } }
MyGenericArrayList
// The translated code public class MyGenericArrayList { private int size; // number of elements private Object[] elements; public MyGenericArrayList() { // constructor elements = new Object[10]; // allocate initial capacity of 10 size = 0; } // Compiler replaces E with Object, but check e is of type E, when invoked to ensure type-safety public void add(Object e) { if (size < elements.length) { elements[size] = e; } else { // allocate a larger array and add the element, omitted } ++size; } // Compiler replaces E with Object, and insert downcast operator (E) for the return type when invoked public Object get(int index) { if (index >= size) throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size); return (Object)elements[index]; } public int size() { return size; } }
public class MyGenericArrayListTest { public static void main(String[] args) { // type safe to hold a list of Strings MyGenericArrayListstrLst = new MyGenericArrayList (); strLst.add("alpha"); // compiler checks if argument is of type String strLst.add("beta"); for (int i = 0; i < strLst.size(); ++i) { String str = strLst.get(i); // compiler inserts the downcasting operator (String) System.out.println(str); } strLst.add(new Integer(1234)); // compiler detected argument is NOT String, issues compilation error } }
2.2 泛型方法
方法也可以定義為泛型類型,例如:
public static在泛型方法中,需要在返回類型之前聲明類型參數,這樣類型參數就可以在方法的參數列表或者返回類型上使用了。void ArrayToArrayList(E[] a, ArrayList lst) { for (E e : a) lst.add(e); }
和泛型類相似,當編譯器也會使用Object類型來替換參數類型E,例如上面的代碼被編譯器處理後形式如下:
public static void ArrayToArrayList(Object[] a, ArrayList lst) { // compiler checks if a is of type E[], // lst is of type ArrayListfor (Object e : a) lst.add(e); // compiler checks if e is of type E }
import java.util.*; public class TestGenericMethod { public staticvoid ArrayToArrayList(E[] a, ArrayList lst) { for (E e : a) lst.add(e); } public static void main(String[] args) { ArrayList lst = new ArrayList (); Integer[] intArray = {55, 66}; // autobox ArrayToArrayList(intArray, lst); for (Integer i : lst) System.out.println(i); String[] strArray = {"one", "two", "three"}; //ArrayToArrayList(strArray, lst); // Compilation Error below } }
另外,在泛型方法中,泛型有一個可選的語法就是指定泛型方法中的類型。你可以將指定的真實類型放在點操作符和方法名之間。
TestGenericMethod.這個語法可以增加代碼的可讀性,另外可以在類型模糊的地方來指定泛型類型。ArrayToArrayList(intArray, lst);
2.3 通配符
對於下面這行代碼
ArrayList它會出現類型不兼容的錯誤,因為ArrayList
非受限的通配符
為了解決這個問題,泛型中引入了一個通配符(?),它代表任何未知類型,例如我們可以重寫上面的printList()方法,它可以接受任何未知類型的List。
public static void printList(List lst) { for (Object o : lst) System.out.println(o); }
通配符表示接受類型type以及它的子類,例如:
public static void printList(List lst) { for (Object o : lst) System.out.println(o); }
很顯然,可以理解為,因為它可以接受任何對象類型。
下限通配符
跟上限通配符類似,表示接受的類型是type以及type的父類
2.4 受限泛型
在使用泛型的時候,我們也可以使用上面的限制來指定參數類型。例如:
例子
下面方法add()中聲明了參數類型
public class MyMath { public staticdouble add(T first, T second) { return first.doubleValue() + second.doubleValue(); } public static void main(String[] args) { System.out.println(add(55, 66)); // int -> Integer System.out.println(add(5.5f, 6.6f)); // float -> Float System.out.println(add(5.5, 6.6)); // double -> Double } }
例如:
public class TestGenericsMethod { public static> T maximum(T x, T y) { return (x.compareTo(y) > 0) ? x : y; } public static void main(String[] args) { System.out.println(maximum(55, 66)); System.out.println(maximum(6.6, 5.5)); System.out.println(maximum("Monday", "Tuesday")); } }
public static Comparable maximum(Comparable x, Comparable y) { // replace T by upper bound type Comparable // Compiler checks x, y are of the type Comparable // Compiler inserts a type-cast for the return value return (x.compareTo(y) > 0) ? x : y; }
(Comparable)maximum(55, 66); (Comparable)maximum(6.6, 5.5); (Comparable)maximum("Monday", "Tuesday");