當我們在定義類,接口和方法時,可以接收一個類型作爲參數,這就叫做泛型。
函數可以傳入普通的參數,也可以傳入一個類型參數。不同之處是普通的參數就是值而已,但是類型參數卻是個類型。
使用泛型的好處:
- 強類型檢查。在編譯時就可以得到類型錯誤信息。
- 避免顯式強制轉換。
- 方便實現通用算法。
對類使用泛型
我們可以創建一個簡單的Class Box。它提供存取一個類型爲Object的對象。
public class Box {
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
private Object object;
你可以傳給它任何你想要的對象,比如對象String,Integer等,也可以傳入自定義的一些對象。但是調用getObject方法返回的對象需要顯式的強轉爲傳入的類型,才能使用原來類型的一些方法。
我們可以使用泛型來構造這個對象。
public class Box<T> {
public T getObject() {
return object;
}
private T object;
public void setObject(T object) {
this.object = object;
}
}
我們可以看到,所有的Object被替換成了T。T代表了某種類型,你在實例化Box對象時,必須要給其指定一種類型,String,Integer或者自定義的類,並且調用getObject方法並不需要進行強轉就可以使用該類型的方法。
一般來說,類型參數名稱越簡單越好,並且需要是大寫的。爲了方便,我們約定了一些命名使用。
- E Element
- K key
- N Number
- T type
- V value
- S,U,V 第2,3,4個類型
我們可以這樣實例化一個Box類。
Box<Integer> integerBox = new Box<Integer>();
同樣,我們也支持在一個類中傳入多個類型參數。例如下面的Pair對象
public class Pair<T, V> {
private T key;
private V value;
public Pair(T key, V value) {
this.key = key;
this.value = value;
}
public T getKey() {
return key;
}
public V getValue() {
return value;
}
使用方法如下。
Pair<Integer, String> one = new Pair<Integer, String>(1, "one");
Pair<String, String> hello = new Pair<String, String>("hello", "world");
對方法使用泛型
泛型可以作用與方法上,此時泛型參數只能在方法體中使用。而泛型作用於類時,則在整個類中可以使用。
在靜態方法、非靜態方法及構造函數都可以使用泛型。
public class Util {
public static <T, U> boolean compare(Pair<T,U> pair1, Pair<T,U> pair2)
{
return pair1.getKey().equals(pair2.getKey()) && pair1.getValue().equals(pair2.getValue());
}
}
下面是對該靜態方法的使用。
Pair one = new Pair("one", 1);
Pair two = new Pair("two", 2);
assertThat(Util.compare(one, two), is(false));
// pass
對泛型進行限定
默認情況下如果直接使用的話,我們可以給其傳任何值。有時候我們想值允許傳入某個類及它的子類。這時候在聲明泛型時可以使用extends關鍵字。
public class Box<T extends Number> {
public T getObject() {
return object;
}
private T object;
public void setObject(T object) {
this.object = object;
}
}
Box box = new Box();
box.setObject(10); //ok
box.setObject("hello"); //compile-time error
我們也可以給類型參數加多個限定。
<T extends B1 & B2 & B3>
加上限定類或接口以後,我們可以使用泛型參數變量調用該類或接口的方法。
通配符的使用
Java中的List就是一個實現了泛型的類,假如我們寫了一個方法,獲取List中元素的個數。只不過這個方法限定T類型爲Number。
public static int getCount(List<Number> list)
{
int i = 0;
for(Number n : list)
{
i++;
}
return i;
}
然後我們這樣試圖調用它。
List<Integer> list = new ArrayList<Integer>(){
{
add(1);
add(2);
add(3);
}
};
Util.getCount(list); //compile-time error
爲什麼會產生錯誤那?因爲我們要求方法的參數是List,而我們實際傳入的是List。雖然Integer是Number的子類,但是List卻不是List的子類,他們其實是平等的關係。這點一定要注意。我們在方法定義時已經明確表示T的類型是Number了,所以只能接收List,而不能接收其它類型的參數。 這時候
?
通配符就起作用了。我們可以使用?
通配符重新定義這個方法。public class Util {
public static int getCount(List<? extends Number> list)
{
int i = 0;
for(Number n : list)
{
i++;
}
return i;
}
}
List<Integer> list = new ArrayList<Integer>(){
{
add(1);
add(2);
add(3);
}
};
assertThat(Util.getCount(list), is(3)); // pass
既然能限定到一個類及其子類上,當然也能限定到一個類及其父類上。語法如下:
<? supper A>
對泛型使用的總結
-
類型參數不能是原始類型(int, char,double),只能傳入這些類型的封轉類(Integer,Char,Double)。
-
不能直接創建類型參數的實例。
public static <E> void append(List<E> list) { E elem = new E(); // compile-time error list.add(elem);
}
但有通過反射可以實現。
public static <E> void append(List<E> list, Class<E> cls) throws Exception { E elem = cls.newInstance(); // OK list.add(elem);}
你可以這樣調用它:
List<String> ls = new ArrayList<>();
append(ls, String.class);
- 靜態字段的類型不能爲類型參數。
public class Box<T> { private static T object; // compile-time error
}
- 不能創建類型參數變量的數組。
List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error
- 不能重載一個方法,該方法的形參都來自於同一個類型參數對象。
public class Example {
public void print(List<Integer> integers) {}
public void print(List<Double> doubles) {}
}
參考文檔:http://docs.oracle.com/javase/tutorial/java/generics/index.html