例如,類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) {
GenericBox box1 = 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
MyGenericArrayList strLst = 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 ArrayList
for (Object e : a) lst.add(e); // compiler checks if e is of type E
}
import java.util.*;
public class TestGenericMethod {
public static void 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 static double 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");