本文只是做為泛型的入門和基礎的泛型的了解。
什么是泛型?
泛型(generices)是JDK5引入的新特性,這個特性主要是為了創建容器類,這也是泛型出現的重要原因之一。泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。聲明的類型參數在使用時用具體的類型來替換。
1 泛型類
例如我們有一個容器類Container,我們希望,這個容器可以裝一個String類型的變量。代碼如下:
public class Container {
private String obj;
public String getObj() {
return obj;
}
public void setObj(String obj) {
this.obj = obj;
}
}
那么我們可以這么使用這個容器類,例如:
Container container = new Container();
container.setObj("container str.");
//會輸出"container str."
System.out.println(container.getObj());
那,如果我們想在Container里面裝入int類型的話。這個類是不適用的,因為它只有1個String類型的變量obj。那就需要另外聲明一個Container類,然后里面包含一個int類型的變量。機智的你應該可以想到可以將Container的obj對象改成Object類型。這樣就可以存入任何的對象了, 因為Object是所有對象的基類。代碼如下:
public class ContainerObj {
private Object obj;
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
這時候,我們就可以使用ContainerObj來存入任何類型了,只是取出來使用的時候需要進行一下轉換。例如:
ContainerObj containerObj = new ContainerObj();
containerObj.setObj("contailObj str");
//會輸出"contailObj str"
System.out.println(containerObj.getObj());
containerObj.setObj(1);
//會輸出1
int i = (Integer) containerObj.getObj();
System.out.println(i);
這樣子做回有一些缺點,例如,要記住存進去的是什么類型,不然取出來的時候,轉換的類型不對的話就會發生異常。
這時候,我們使用泛型就可以達到既可以不限定類型,又可以取的時候不用轉換。例如下面這個類:
public class ContainerGeneric<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
//調用
//=======使用泛型的類
ContainerGeneric<Integer> containerInt = new ContainerGeneric<>();
containerInt.setObj(10);
//會輸出 10
System.out.println(containerInt.getObj());
ContainerGeneric<String> containerStr = new ContainerGeneric<>();
containerStr.setObj("container str");
//會輸出"container str"
System.out.println(containerStr.getObj());
2 泛型接口
泛型接口和泛型類的使用方式差不多,下面看個簡單的例子:
public interface IContainer<T> {
T getObj();
}
//需要在實現這個接口的時候,指定類型為String
public class ContainerImpl implements IContainer<String> {
//所以重寫getObj方法的時候,需要返回的是String類型
@Override
public String getObj() {
return "invoke getObj ..";
}
}
//=======使用泛型接口
IContainer iContainer = new ContainerImpl();
//會輸出"invoke getObj .."
System.out.println(iContainer.getObj());
3 泛型方法
泛型方法,是在調用方法的時候指明泛型的具體類型。允許將在一個方法使用泛型,而不必整個類使用泛型。在方法返回值之前放上泛型標識<T>就可以了。可以在普通的方法使用泛型,也可以在靜態方法使用泛型,還可以在泛型方法里面使用泛型參數。如下例子:
public class GenericMethod {
//泛型方法
public <T> void outTest(){
System.out.println("outTest is invoke..");
}
//泛型參數
public <T> void outTest(T t, Integer i){
System.out.println("t = ,"+t+"i="+i);
}
//靜態泛型方法
public static <T> void staticOutTest(){
System.out.println("staticOutTest is invoke..");
}
public static void main(String[] args) {
GenericMethod genericMethod = new GenericMethod();
genericMethod.outTest();//輸出"outTest is invoke.."
genericMethod.outTest("str",1);//輸出"t = ,stri=1"
staticOutTest();//輸出"staticOutTest is invoke.."
}
}
4 邊界符
使用泛型方法的過程中,往往會需要限定方法的參數的類型,而不至于可以傳入任意類型而造成混亂。
例如,我們需要寫一個方法,判斷一個對象是否正數。泛型方法可以這么寫。
//判斷一個數是否正數
public static <T> boolean isPositive (T t){
System.out.println(t);
if(t instanceof Integer)
return Integer.parseInt(t.toString()) > 0;
else if(t instanceof Double)
return Double.parseDouble(t.toString()) > 0;
//還需要判斷 Float , Byte , Short , Long 類型
else
return false;
}
//調用:
System.out.println(isPositive(1));// 輸出 1 true
System.out.println(isPositive(0.1d));// 輸出 0.1 true
System.out.println(isPositive("aa"));// 輸出 aa false
因為isPositive方法的參數T是泛型,所以調用的時候可以傳入任意類型。所以在寫方法的時候需要進行所有數字類型的判斷。如果可以限定參數T是數字類型的話,就可以減少很多麻煩和避免程序混亂。這時候可以使用邊界符,邊界符的作用是,限定泛型參數T的類型。使用如下:
//使用邊界符 判斷一個數是否正數
public static <T extends Number> boolean isPositiveBunder (T t){
System.out.println(t);
return t.floatValue()>0;
}
//調用:
System.out.println(isPositiveBunder(1));//輸出 1 true
System.out.println(isPositiveBunder(0.1d));// 輸出 0.1 true
// System.out.println(isPositiveBunder("aa")); 編譯失敗,已經限定了T只能是實現Number抽象類的類
因為使用了邊界符限定了方法isPositiveBunder的參數T必須是實現了抽象類Number的類型,所以調用的時候,只能傳入實現了Number類的類型。String沒有繼承或者實現Number類型,所以不能通過編譯。由于知道參數 t 一定是實現了Number類,所以可以直接使用 Number類的 floatValue 方法來取值進行判斷。
5 通配符
我們知道,通過繼承,可以實現實例化一個子類賦值給父類或接口。例如:
Number num = new Integer(1);
Integer 類實現了繼承了Number這個抽象類。public final class Integer extends Number 。
但是,使用泛型的時候指定的泛型類并沒有這個繼承的關系,例如
// List<Number> numberList = new ArrayList<Integer>();編譯不通過
泛型類ContainerGeneric的參數是Number ,而實例化的時候,指定的參數是 Integer類型。這是不能通過編譯的。使用泛型的時候,默認是沒有父類子類這樣的繼承關系的。但是可以使用通配符,來將子類和父類關聯起來。例如:
//使用通配符,將泛型參數中的繼承關系關聯起來
List<? extends Number> numbers = new ArrayList<Integer>();
<? extends Number>表明可以接受的泛型參數可以是繼承Number類的子類。Integer繼承了Number類。所以可以這么寫,并且在泛型中將繼承關系關聯起來了。
6 PECS原則
如果我們想往numbers添加元素會怎么樣?
//編譯不通過,不能往numbers集合里面添加元素,只能讀取元素
// numbers.add(1);
// numbers.add(2);
為什么不能編譯通過呢?我們可以分析一下
使用通配符的時候我們可以將numbers理解為:
List<? extends Number> numbers = new ArrayList<Integer>();
List<? extends Number> numbers = new ArrayList<Double>();
List<? extends Number> numbers = new ArrayList<Float>();
List<? extends Number> numbers = new ArrayList<Long>();
List<? extends Number> numbers = new ArrayList<Short>();
List<? extends Number> numbers = new ArrayList<Byte>();
numbers并不能確定它實際上的類型是什么類型,如果你往numbers添加一個int 元素. 但是可能numbers實際上可能裝載的是Double元素。所以不能往實現了<? extends T>的類里面添加元素,只能讀取元素。
如果我們確實需要添加元素怎么辦呢?可以使用<? super T >。表明,參數類一定是T的父類,T一定是參數類的子類。如下例子:
List<? super Integer> numberIntList= new ArrayList<Number>();
// List<? super Number> numberList = new ArrayList<Integer>();編譯不通過,Number不是Integer的子類
numberIntList.add(10);//可以通過編譯
可以往實現了<? super T >的類添加元素,但是不能獲取元素。為什么呢?我們一樣可以分析一下。同樣的,使用了spuer關鍵字的通配符,我們可以理解為,"?" 一定匹配的是Integer類的父類或者自己本類型。
List<? super Integer> numberIntList= new ArrayList<Integer>();
List<? super Integer> numberIntList= new ArrayList<Number>();
List<? super Integer> numberIntList= new ArrayList<Object>();
如果我們添加元素的話,可以添加Integer類型,或者Number類型,或者Object類型。但是取的時候,編譯器并不能判斷取出來的是什么類型。有可能是Integer,有可能是Number,有可能是Object類型。所以我們無法取出元素。
根據上面的例子,我們可以總結出一條規律源鉆,”Producer Extends, Consumer Super”(PECS):
“Producer Extends” - 如果你需要一個只讀List,用它來produce T,那么使用? extends T。
“Consumer Super” - 如果你需要一個只寫List,用它來consume T,那么使用? super T。
如果需要同時讀取以及寫入,那么我們就不能使用通配符了。