參考:http://docs.oracle.com/javase/tutorial/java/generics/index.html
爲什麼要使用泛型?
- 更強更嚴格的編譯期間類型檢查
- 淘汰類型造型
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
如果有泛型,則不需要造型
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0); // no cast
- 實現使用泛型的算法,相同的代碼可以運用於不同類型的集合
class name<T1, T2, ..., Tn> { /* ... */ }
1.1 泛型類實例:public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
類型變量(實例中的T)可以在類體內當做類型來使用,類型變量可以是主類型以外的任何類型:任何的類、任何接口、任何類型的數組、甚至其他類型變量。- E - Element (廣泛使用在Java的集合框架)
- K - Key
- N - Number
- T - Type
- V - Value
- S,U,V etc. - 2nd, 3rd, 4th types
Box<Integer> integerBox;
Box<Integer> integerBox = new Box<Integer>();
但是在Java SE 7之後,new之後的類型參數可以忽略,但是<>還是要保留:Box<Integer> integerBox = new Box<>();
1.4 多個類型參數
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
使用這個泛型類時要提供多個對應的類型替換其中的類型參數:Pair<String, Integer> p1 = new OrderddPair<String, Integer>("Even", 8); //Java SE 7之後,可以省略new後面的類型參數 new OrderedPair<>("Even", 8);
Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world");
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
創建一個參數化類型需要將T替換成一個實際的類型比如IntegerBox<Integer> intBox = new Box<>();
但是原類型就不需要替換T,也不需要<>:Box rawBox = new Box();
所以Box就是泛型類Box<T>的原類型,但是那些並非泛型的普通類就不是原類型。Box<String> stringBox = new Box<>();
Box rawBox = stringBox; //沒問題
但是如果將一個原類型賦給參數化類型就會有warning:Box rawBox = new Box();
Box<Integer> intBox = rawBox; //Warning: unchecked conversion
使用原類型調用泛型方法也會有warning:Box<String> stringBox = new Box<>();
Box rawBox = stringBox; //可以的
rawBox.set(8); //waring: unchecked invocation to set(T)
2.2 Unchecked Error Messages
public class WarningDemo {
public static void main(String[] args){
Box<Integer> bi;
bi = createBox(); //unchecked error
}
static Box createBox(){
return new Box();
}
}
public class Util {
// Generic static method
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
public class Pair<K, V> {
private K key;
private V value;
// Generic constructor
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
// Generic methods
public void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
public K getKey() { return key; }
public V getValue() { return value; }
}
使用方法如下:Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2); //這裏明確給出了泛型方法的參數類型,但是其實可以省略,編譯器會自動查找並添加正確的類型參數,可以用這個代替:boolean same = Util.compare(p1, p2); 省略參數類型的特性叫做類型推斷(type inference),它可以讓我們像普通方法一樣調用泛型方法。
4. 參數類型限制
在使用泛型時,有時想要限制使用的類型種類,比如一個操作數字的泛型方法只接受Number對象或者其子類對象,這種需求可以通過參數類型限制實現。
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public <U <strong>extends Number</strong>> void inspect(U u){
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.set(new Integer(10));
integerBox.inspect("some text"); // <strong>error: this is still String! 編譯器報錯</strong>
}
}
public class NaturalNumber<T extends Integer> {
private T n;
public NaturalNumber(T n) { this.n = n; }
public boolean isEven() {
return n.intValue() % 2 == 0;
}
// ...
}
Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }
class D <T extends A & B & C> { /* ... */ } //如果換成class D<T extends B & A & C> { /* ... */ },則編譯器會報錯
泛型方法中的參數類型限制:
參數類型限制是實現泛型算法的關鍵,下例中的方法計算出數組T[]中大於elem的元素個數。如果不使用參數限制將無法編譯,因爲大於運算符'>'只能用於主類型中
public static <T> int countGreaterThan(T[] anArray, T elem) {
int count = 0;
for (T e : anArray)
if (e > elem) // compiler error
++count;
return count;
}
比較對象不能使用‘>',我們應該使用對象的compareTo()方法,這樣的話我們需要將泛型類型限制爲泛型接口Comparable<T>,修改代碼如下public static <T <strong>extends Comparable<T></strong>> int countGreaterThan(T[] anArray, T elem) {
int count = 0;
for (T e : anArray)
if (e.<strong>compareTo</strong>(elem) > 0)
++count;
return count;
}
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); // OK
box.add(new Double(10.1)); // OK
現在有如下的一個泛型方法,思考下它可以接受哪些類型?
public void boxTest(Box<Number> n) { /* ... */ }
從下圖中可以看出,這個方法只接受類型爲Box<Number>的參數,其他的如Box<Integer>其實並非Box<Number>的子類,不滿足“is a”原則。現在我們自己定義一個List接口,PayloadList,這個接口會爲每個list元素配置一個泛型爲P的可選值,聲明如下:
interface PayloadList<E,P> extends List<E> {
void setPayload(int index, P val);
...
}
那麼下面這些類型都是List<String>的子類PayloadList<String,String>
PayloadList<String,Integer>
PayloadList<String,Exception>
6. 類型推斷
static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>());
public class BoxDemo {
public static <U> void addBox(U u,
java.util.List<Box<U>> boxes) {
Box<U> box = new Box<>();
box.set(u);
boxes.add(box);
}
public static <U> void outputBoxes(java.util.List<Box<U>> boxes) {
int counter = 0;
for (Box<U> box: boxes) {
U boxContents = box.get();
System.out.println("Box #" + counter + " contains [" +
boxContents.toString() + "]");
counter++;
}
}
public static void main(String[] args) {
java.util.ArrayList<Box<Integer>> listOfIntegerBoxes =
new java.util.ArrayList<>();
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes); //正常的調用方法,不用推斷
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes); //去掉<Integer>,但是編譯器可以推斷出addBox()方法的類型爲<Integer>
BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes); //同上
BoxDemo.outputBoxes(listOfIntegerBoxes); //同樣,listOfIntegerBoxes中已經知道類型爲<Integer>,編譯器推斷出outputBoxes()方法類型也是<Integer>
}
}
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
//可以用下面的語句代替
Map<String, List<String>> myMap = new HashMap<>();
但是創建泛型類對象時<>不能省,否則會包unchecked conversion警告:Map<String, List<String>> myMap = new HashMap(); // unchecked conversion warning
6.3 泛型類非泛型類的泛型構建器中的類型推斷
class MyClass<X> {
<T> MyClass(T t) {
// ...
}
}
new一個對象是可以這樣:new MyClass<Integer>("")
注意這個new語句指明瞭類中的類型參數爲Integer,構建器中的參數類型沒有明確指定,編譯器推斷出類型爲String。static <T> List<T> emptyList();
List<String> listOne = Collections.emptyList(); //這個語句的目的是獲取List<String>實例,String就是目標類型,編譯器據此推斷出List<T>的類型是List<String>
但是在SE7的有些情形中,編譯器不能推斷出類型,比如下面的函數使用了List<String>類型參數void processStringList(List<String> stringList) {
// process stringList
}
我們想通過如下語句調用這個方法,但是這樣的話SE 7會得到編譯錯誤:List<Object> cannot be converted to List<String>,SE 8可以編譯通過:processStringList(Collections.emptyList());
SE 7中需要明確指定emptyList()返回的參數類型:processStringList(Collections.<String>emptyList());
7. 通配符
7.1 上邊界通配符
- 想寫一個可以用Object類裏的功能實現的方法
- 代碼裏用到不使用類型參數的泛型類中的方法,比如List.size()或者List.clear(),Class<?>被經常用到,因爲Class<T>裏的大多數方法都不使用T
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
想達到打印任何類型List的目的,就要使用無邊界通配符List<?>,因爲對任何的實際類型A,List<A>都是List<?>的子類型public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
7.3 下邊界通配符
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
7.4 通配符和子類型化
List<B> lb = new ArrayList<>();
List<A> la = lb; // compile-time error,因爲List<A>和List<B>之間是沒有關係的,他們共同的父類是List<?>
可通過定義上邊界的方法創建有父子關係的類List<? extends Integer> intList = new ArrayList<>();
List<? extends Number> numList = intList; //List<? extends Integer>是List<? extends Number>的子類
完全的關係圖如下:編譯器有時可以通過代碼上下文推斷出通配符的確定類型,叫做通配符匹配。
import java.util.List;
public class WildcardError {
void foo(List<?> i) {
i.set(0, i.get(0));
}
}
原因是從編譯器的角度看,方法foo的參數List<?> i是一個類型爲Object的List。編譯器認爲你傳了一個錯誤的類型給參數,泛型就是用來防止這種情況的,使用泛型可以進行跟強的類型檢查。public class WildcardFixed {
void foo(List<?> i) {
fooHelper(i);
}
// Helper method created so that the wildcard can be captured
// through type inference.
<strong>private <T> void fooHelper(List<T> l) {
l.set(0, l.get(0));
}</strong>
}
import java.util.List;
public class WildcardErrorBad {
void swapFirst(List<? extends Number> l1, List<? extends Number> l2) {
Number temp = l1.get(0);
l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
// got a CAP#2 extends Number;
// same bound, but different types
l2.set(0, temp); // expected a CAP#1 extends Number,
// got a Number
}
}
這個例子中,代碼裏有一些不安全的操作。比如像下面這樣調用swapFirst方法,雖然List<Integer>和List<Double>都滿足List<? extends Number>的條件,但是將Integer對象插入一個Double類型的List顯示是不合法的。List<Integer> li = Arrays.asList(1, 2, 3);
List<Double> ld = Arrays.asList(10.10, 20.20, 30.30);
swapFirst(li, ld);
這種情況是不能通過helper方法來解決的,因爲這個代碼本身就是錯誤的。- “in”變量一般用上邊界通配符,使用extends關鍵字
- “out”變量用下邊界通配符,使用super關鍵字
- 如果“in”變量可以被類中的方法訪問,則定義爲無邊界通配符
- 既是“in”又是“out”的變量,不使用通配符
- 如果類型參數是無邊界的,編譯器會用Object取代所有泛型類型參數,如果是有邊界的,則用邊界類取代所有泛型類型參數,這種情況下,字節碼就只包含普通的類、接口以及方法了
- 必要時插入類型造型,保護類型安全
- 自動生成橋方法,以保留擴展泛型類型的多態性
public class Node<T> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) }
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}
無邊界的泛型類型T被替換成Object,如下public class Node {
private Object data;
private Node next;
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
public Object getData() { return data; }
// ...
}
public class Node<T extends Comparable<T>> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}
泛型類型T被替換成Comparablepublic class Node {
private Comparable data;
private Node next;
public Node(Comparable data, Node next) {
this.data = data;
this.next = next;
}
public Comparable getData() { return data; }
// ...
}
8.2 泛型方法的類型清除
// Counts the number of occurrences of elem in anArray.
//
public static <T> int count(T[] anArray, T elem) {
int cnt = 0;
for (T e : anArray)
if (e.equals(elem))
++cnt;
return cnt;
}
T被替換成Object類public static int count(Object[] anArray, Object elem) {
int cnt = 0;
for (Object e : anArray)
if (e.equals(elem))
++cnt;
return cnt;
}
如以下的繼承關係class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }
我們可以寫一個泛型方法畫不同的形狀public static <T extends Shape> void draw(T shape) { /* ... */ }
方法中的T會被替換成邊界類Shapepublic static void draw(Shape shape) { /* ... */ }