一、引入
1、泛型是什么
首先告訴大家ArrayList就是泛型。那ArrayList能完成哪些想不到的功能呢?先看看下面這段代碼:
ArrayList<String> strList = new ArrayList<String>();
ArrayList<Integer> intList = new ArrayList<Integer>();
ArrayList<Double> doubleList = new ArrayList<Double>();
大家對ArrayList很熟悉,這里構(gòu)造了三個(gè)List,分別盛裝String、Integer和Double;這就是ArrayList的過人之處:即各種類型的變量都可以組裝成對應(yīng)的List,而不必針對每個(gè)類型分別實(shí)現(xiàn)一個(gè)構(gòu)建ArrayList的類。這里可能看不懂,開篇總是困難的,下面看看如果沒有泛型的話,我們要怎么做;
2、沒有泛型會(huì)怎樣
先看下面這段代碼:
我們實(shí)現(xiàn)兩個(gè)能夠設(shè)置點(diǎn)坐標(biāo)的類,分別設(shè)置Integer類型的點(diǎn)坐標(biāo)和Float類型的點(diǎn)坐標(biāo):
//設(shè)置Integer類型的點(diǎn)坐標(biāo)
class IntegerPoint{
private Integer x ; // 表示X坐標(biāo)
private Integer y ; // 表示Y坐標(biāo)
public void setX(Integer x){
this.x = x ;
}
public void setY(Integer y){
this.y = y ;
}
public Integer getX(){
return this.x ;
}
public Integer getY(){
return this.y ;
}
}
//設(shè)置Float類型的點(diǎn)坐標(biāo)
class FloatPoint{
private Float x ; // 表示X坐標(biāo)
private Float y ; // 表示Y坐標(biāo)
public void setX(Float x){
this.x = x ;
}
public void setY(Float y){
this.y = y ;
}
public Float getX(){
return this.x ;
}
public Float getY(){
return this.y ;
}
}
那現(xiàn)在有個(gè)問題:大家有沒有發(fā)現(xiàn),他們除了變量類型不一樣,一個(gè)是Integer一個(gè)是Float以外,其它并沒有什么區(qū)別!那我們能不能合并成一個(gè)呢?
答案是可以的,因?yàn)镮nteger和Float都是派生自O(shè)bject的,我們用下面這段代碼代替:
class ObjectPoint{
private Object x ;
private Object y ;
public void setX(Object x){
this.x = x ;
}
public void setY(Object y){
this.y = y ;
}
public Object getX(){
return this.x ;
}
public Object getY(){
return this.y ;
}
}
即全部都用Object來代替所有的子類;
在使用的時(shí)候是這樣的:
ObjectPoint integerPoint = new ObjectPoint();
integerPoint.setX(new Integer(100));
Integer integerX=(Integer)integerPoint.getX();
在設(shè)置的時(shí)候,使用new Integer(100)來新建一個(gè)Integer
integerPoint.setX(new Integer(100));
然后在取值的時(shí)候,進(jìn)行強(qiáng)制轉(zhuǎn)換:
Integer integerX=(Integer)integerPoint.getX();
由于我們設(shè)置的時(shí)候,是設(shè)置的Integer,所以在取值的時(shí)候,強(qiáng)制轉(zhuǎn)換是不會(huì)出錯(cuò)的。
同理,F(xiàn)loatPoint的設(shè)置和取值也是類似的,代碼如下:
ObjectPoint floatPoint = new ObjectPoint();
floatPoint.setX(new Float(100.12f));
Float floatX = (Float)floatPoint.getX();
但問題來了:注意,注意,我們這里使用了強(qiáng)制轉(zhuǎn)換,我們這里setX()和getX()寫得很近,所以我們明確的知道我們傳進(jìn)去的是Float類型,那如果我們記錯(cuò)了呢?
比如我們改成下面這樣,編譯時(shí)會(huì)報(bào)錯(cuò)嗎:
ObjectPoint floatPoint = new ObjectPoint();
floatPoint.setX(new Float(100.12f));
String floatX = (String)floatPoint.getX();
不會(huì)?。?!我們問題的關(guān)鍵在于這句:
String floatX = (String)floatPoint.getX();
強(qiáng)制轉(zhuǎn)換時(shí),會(huì)不會(huì)出錯(cuò)。因?yàn)榫幾g器也不知道你傳進(jìn)去的是什么,而floatPoint.getX()返回的類型是Object,所以編譯時(shí),將Object強(qiáng)轉(zhuǎn)成String是成立的。必然不會(huì)報(bào)錯(cuò)。
而在運(yùn)行時(shí),則不然,在運(yùn)行時(shí),floatPoint實(shí)例中明明傳進(jìn)去的是Float類型的變量,非要把它強(qiáng)轉(zhuǎn)成String類型,肯定會(huì)報(bào)類型轉(zhuǎn)換錯(cuò)誤的!
那有沒有一種辦法在編譯階段,即能合并成同一個(gè),又能在編譯時(shí)檢查出來傳進(jìn)去類型不對呢?當(dāng)然,這就是泛型。
下面我們將對泛型的寫法和用法做一一講解。
二、各種泛型定義及使用
1、泛型類定義及使用
我們先看看泛型的類是怎么定義的:
//定義
class Point<T>{// 此處可以隨便寫標(biāo)識(shí)符號(hào)
private T x ;
private T y ;
public void setX(T x){//作為參數(shù)
this.x = x ;
}
public void setY(T y){
this.y = y ;
}
public T getX(){//作為返回值
return this.x ;
}
public T getY(){
return this.y ;
}
};
//IntegerPoint使用
Point<Integer> p = new Point<Integer>() ;
p.setX(new Integer(100)) ;
System.out.println(p.getX());
//FloatPoint使用
Point<Float> p = new Point<Float>() ;
p.setX(new Float(100.12f)) ;
System.out.println(p.getX());
先看看運(yùn)行結(jié)果:
從結(jié)果中可以看到,我們實(shí)現(xiàn)了開篇中IntegerPoint類和FloatPoint類的效果。下面來看看泛型是怎么定義及使用的吧。
(1)、定義泛型:Point<T>
首先,大家可以看到Point<T>,即在類名后面加一個(gè)尖括號(hào),括號(hào)里是一個(gè)大寫字母。這里寫的是T,其實(shí)這個(gè)字母可以是任何大寫字母,大家這里先記著,可以是任何大寫字母,意義是相同的。
(2)類中使用泛型
這個(gè)T表示派生自O(shè)bject類的任何類,比如String,Integer,Double等等。這里要注意的是,T一定是派生于Object類的。為方便起見,大家可以在這里把T當(dāng)成String,即String在類中怎么用,那T在類中就可以怎么用!所以下面的:定義變量,作為返回值,作為參數(shù)傳入的定義就很容易理解了。
//定義變量
private T x ;
//作為返回值
public T getX(){
return x ;
}
//作為參數(shù)
public void setX(T x){
this.x = x ;
}
(3)使用泛型類
下面是泛型類的用法:
//IntegerPoint使用
Point<Integer> p = new Point<Integer>() ;
p.setX(new Integer(100)) ;
System.out.println(p.getX());
//FloatPoint使用
Point<Float> p = new Point<Float>() ;
p.setX(new Float(100.12f)) ;
System.out.println(p.getX());
首先,是構(gòu)造一個(gè)實(shí)例:
Point<String> p = new Point<String>() ;
這里與普通構(gòu)造類實(shí)例的不同之點(diǎn)在于,普通類構(gòu)造函數(shù)是這樣的:Point p = new Point() ;
而泛型類的構(gòu)造則需要在類名后添加上<String>,即一對尖括號(hào),中間寫上要傳入的類型。
因?yàn)槲覀儤?gòu)造時(shí),是這樣的:class Point<T>,所以在使用的時(shí)候也要在Point后加上類型來定義T代表的意義。
然后在getVar()和setVar()時(shí)就沒有什么特殊的了,直接調(diào)用即可。
從上面的使用時(shí),明顯可以看出泛型的作用,在構(gòu)造泛型類的實(shí)例的時(shí)候:
//IntegerPoint使用
Point<Integer> p = new Point<Integer>() ;
//FloatPoint使用
Point<Float> p = new Point<Float>() ;
尖括號(hào)中,你傳進(jìn)去的是什么,T就代表什么類型。這就是泛型的最大作用,我們只需要考慮邏輯實(shí)現(xiàn),就能拿給各種類來用。
前面我們提到ArrayList也是泛型,我們順便它的實(shí)現(xiàn):
public class ArrayList<E>{
…………
}
看到了吧,跟我們的Point實(shí)現(xiàn)是一樣的,這也就是為什么ArrayList能夠盛裝各種類型的主要原因。
(4)使用泛型實(shí)現(xiàn)的優(yōu)勢
相比我們開篇時(shí)使用Object的方式,有兩個(gè)優(yōu)點(diǎn):
(1)、不用強(qiáng)制轉(zhuǎn)換
//使用Object作為返回值,要強(qiáng)制轉(zhuǎn)換成指定類型
Float floatX = (Float)floatPoint.getX();
//使用泛型時(shí),不用強(qiáng)制轉(zhuǎn)換,直接出來就是String
System.out.println(p.getVar());
(2)、在settVar()時(shí)如果傳入類型不對,編譯時(shí)會(huì)報(bào)錯(cuò)
可以看到,當(dāng)我們構(gòu)造時(shí)使用的是String,而在setVar時(shí),傳進(jìn)去Integer類型時(shí),就會(huì)報(bào)錯(cuò)。而不是像Object實(shí)現(xiàn)方式一樣,在運(yùn)行時(shí)才會(huì)報(bào)強(qiáng)制轉(zhuǎn)換錯(cuò)誤。
2、多泛型變量定義及字母規(guī)范
(1)、多泛型變量定義
上在我們只定義了一個(gè)泛型變量T,那如果我們需要傳進(jìn)去多個(gè)泛型要怎么辦呢?
只需要在類似下面這樣就可以了:
class MorePoint<T,U>{
}
也就是在原來的T后面用逗號(hào)隔開,寫上其它的任意大寫字母即可。想加幾個(gè)就加幾個(gè),比如我們想加五個(gè)泛型變量,那應(yīng)該是這樣的:
class MorePoint<T,U,A,B,C>{
}
舉個(gè)粟子,我們在Point上再另加一個(gè)字段name,也用泛型來表示,那要怎么做?代碼如下:
class MorePoint<T,U> {
private T x;
private T y;
private U name;
public void setX(T x) {
this.x = x;
}
public T getX() {
return this.x;
}
…………
public void setName(U name){
this.name = name;
}
public U getName() {
return this.name;
}
}
//使用
MorePoint<Integer,String> morePoint = new MorePoint<Integer, String>();
morePoint.setName("harvic");
Log.d(TAG, "morPont.getName:" + morePoint.getName());
從上面的代碼中,可以明顯看出,就是在新添加的泛型變量U用法與T是一樣的。
(2)、字母規(guī)范
在定義泛型類時(shí),我們已經(jīng)提到用于指定泛型的變量是一個(gè)大寫字母:
class Point<T>{
…………
}
當(dāng)然不是的?。。?!任意一個(gè)大寫字母都可以。他們的意義是完全相同的,但為了提高可讀性,大家還是用有意義的字母比較好,一般來講,在不同的情境下使用的字母意義如下:
- E — Element,常用在java Collection里,如:List<E>,Iterator<E>,Set<E>
- K,V — Key,Value,代表Map的鍵值對
- N — Number,數(shù)字
- T — Type,類型,如String,Integer等等
如果這些還不夠用,那就自己隨便取吧,反正26個(gè)英文字母呢。
再重復(fù)一遍,使用哪個(gè)字母是沒有特定意義的!只是為了提高可讀性?。。?!
3、泛型接口定義及使用
在接口上定義泛型與在類中定義泛型是一樣的,代碼如下:
interface Info<T>{ // 在接口上定義泛型
public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型類型
public void setVar(T x);
}
與泛型類的定義一樣,也是在接口名后加尖括號(hào);
(1)、使用方法一:非泛型類
但是在使用的時(shí)候,就出現(xiàn)問題了,我們先看看下面這個(gè)使用方法:
class InfoImpl implements Info<String>{ // 定義泛型接口的子類
private String var ; // 定義屬性
public InfoImpl(String var){ // 通過構(gòu)造方法設(shè)置屬性內(nèi)容
this.setVar(var) ;
}
@Override
public void setVar(String var){
this.var = var ;
}
@Override
public String getVar(){
return this.var ;
}
}
public class GenericsDemo24{
public void main(String arsg[]){
InfoImpl i = new InfoImpl("harvic");
System.out.println(i.getVar()) ;
}
};
首先,先看InfoImpl的定義:
class InfoImpl implements Info<String>{
…………
}
要清楚的一點(diǎn)是InfoImpl不是一個(gè)泛型類!因?yàn)樗惷鬀]有<T>!
然后在在這里我們將Info<String>中的泛型變量T定義填充為了String類型。所以在重寫時(shí)setVar()和getVar()時(shí),IDE會(huì)也我們直接生成String類型的重寫函數(shù)。
最后在使用時(shí),沒什么難度,傳進(jìn)去String類型的字符串來構(gòu)造InfoImpl實(shí)例,然后調(diào)用它的函數(shù)即可。
public class GenericsDemo24{
public void main(String arsg[]){
InfoImpl i = new InfoImpl("harvic");
System.out.println(i.getVar()) ;
}
};
(2)、使用方法二:泛型類
在方法一中,我們在類中直接把Info<T>接口給填充好了,但我們的類,是可以構(gòu)造成泛型類的,那我們利用泛型類來構(gòu)造填充泛型接口會(huì)是怎樣呢?
interface Info<T>{ // 在接口上定義泛型
public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型類型
public void setVar(T var);
}
class InfoImpl<T> implements Info<T>{ // 定義泛型接口的子類
private T var ; // 定義屬性
public InfoImpl(T var){ // 通過構(gòu)造方法設(shè)置屬性內(nèi)容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
}
public class GenericsDemo24{
public static void main(String arsg[]){
InfoImpl<String> i = new InfoImpl<String>("harvic");
System.out.println(i.getVar()) ;
}
};
最關(guān)鍵的是構(gòu)造泛型類的過程:
class InfoImpl<T> implements Info<T>{ // 定義泛型接口的子類
private T var ; // 定義屬性
public InfoImpl(T var){ // 通過構(gòu)造方法設(shè)置屬性內(nèi)容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
}
在這個(gè)類中,我們構(gòu)造了一個(gè)泛型類InfoImpl<T>,然后把泛型變量T傳給了Info<T>,這說明接口和泛型類使用的都是同一個(gè)泛型變量。
然后在使用時(shí),就是構(gòu)造一個(gè)泛型類的實(shí)例的過程,使用過程也不變。
public class GenericsDemo24{
public static void main(String arsg[]){
Info<String> i = new InfoImpl<String>("harvic");
System.out.println(i.getVar()) ;
}
};
使用泛型類來繼承泛型接口的作用就是讓用戶來定義接口所使用的變量類型,而不是像方法一那樣,在類中寫死。
那我們稍微加深點(diǎn)難度,構(gòu)造一個(gè)多個(gè)泛型變量的類,并繼承自Info接口:
class InfoImpl<T,K,U> implements Info<U>{ // 定義泛型接口的子類
private U var ;
private T x;
private K y;
public InfoImpl(U var){ // 通過構(gòu)造方法設(shè)置屬性內(nèi)容
this.setVar(var) ;
}
public void setVar(U var){
this.var = var ;
}
public U getVar(){
return this.var ;
}
}
在這個(gè)例子中,我們在泛型類中定義三個(gè)泛型變量T,K,U并且把第三個(gè)泛型變量U用來填充接口Info。所以在這個(gè)例子中Info所使用的類型就是由U來決定的。
使用時(shí)是這樣的:泛型類的基本用法,不再多講,代碼如下:
public class GenericsDemo24{
public void main(String arsg[]){
InfoImpl<Integer,Double,String> i = new InfoImpl<Integer,Double,String>("harvic");
System.out.println(i.getVar()) ;
}
}
4、泛型函數(shù)定義及使用
上面我們講解了類和接口的泛型使用,下面我們再說說,怎么單獨(dú)在一個(gè)函數(shù)里使用泛型。比如我們在新建一個(gè)普通的類StaticFans,然后在其中定義了兩個(gè)泛型函數(shù):
public class StaticFans {
//靜態(tài)函數(shù)
public static <T> void StaticMethod(T a){
Log.d("harvic","StaticMethod: "+a.toString());
}
//普通函數(shù)
public <T> void OtherMethod(T a){
Log.d("harvic","OtherMethod: "+a.toString());
}
}
上面分別是靜態(tài)泛型函數(shù)和常規(guī)泛型函數(shù)的定義方法,與以往方法的唯一不同點(diǎn)就是在返回值前加上<T>來表示泛型變量。其它沒什么區(qū)別。
使用方法如下:
//靜態(tài)方法
StaticFans.StaticMethod("adfdsa");//使用方法一
StaticFans.<String>StaticMethod("adfdsa");//使用方法二
//常規(guī)方法
StaticFans staticFans = new StaticFans();
staticFans.OtherMethod(new Integer(123));//使用方法一
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
結(jié)果如下:
首先,我們看靜態(tài)泛型函數(shù)的使用方法:
StaticFans.StaticMethod("adfdsa");//使用方法一
StaticFans.<String>StaticMethod("adfdsa");//使用方法二
從結(jié)果中我們可以看到,這兩種方法的結(jié)果是完全一樣的,但他們還有些區(qū)別的,區(qū)別如下:
- 方法一,可以像普通方法一樣,直接傳值,任何值都可以(但必須是派生自O(shè)bject類的類型,比如String,Integer等),函數(shù)會(huì)在內(nèi)部根據(jù)傳進(jìn)去的參數(shù)來識(shí)別當(dāng)前T的類別。但盡量不要使用這種隱式的傳遞方式,代碼不利于閱讀和維護(hù)。因?yàn)閺耐庥^根本看不出來你調(diào)用的是一個(gè)泛型函數(shù)。
- 方法二,與方法一不同的地方在于,在調(diào)用方法前加了一個(gè)<String>來指定傳給<T>的值,如果加了這個(gè)<String>來指定參數(shù)的值的話,那StaticMethod()函數(shù)里所有用到的T類型也就是強(qiáng)制指定了是String類型。這是我們建議使用的方式。
同樣,常規(guī)泛型函數(shù)的使用也有這兩種方式:
StaticFans staticFans = new StaticFans();
staticFans.OtherMethod(new Integer(123));//使用方法一
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
可以看到,與平常一樣,先創(chuàng)建類的實(shí)例,然后調(diào)用泛型函數(shù)。
方法一,隱式傳遞了T的類型,與上面一樣,不建議這么做。
方法二,顯示將T賦值為Integer類型,這樣OtherMethod(T a)傳遞過來的參數(shù)如果不是Integer那么編譯器就會(huì)報(bào)錯(cuò)。
進(jìn)階:返回值中存在泛型
上面我們的函數(shù)中,返回值都是void,但現(xiàn)實(shí)中不可能都是void,有時(shí),我們需要將泛型變量返回,比如下面這個(gè)函數(shù):
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
函數(shù)返回值是List<T>類型。至于傳入?yún)?shù)Class<T> object的意義,我們下面會(huì)講。這里也就是想通過這個(gè)例子來告訴大家,泛型變量其實(shí)跟String,Integer,Double等等的類的使用上沒有任何區(qū)別,T只是一個(gè)符號(hào),可以代表String,Integer,Double……這些類的符號(hào),在泛型函數(shù)使用時(shí),直接把T看到String,Integer,Double……中的任一個(gè)來寫代碼就可以了。唯一不同的是,要在函數(shù)定義的中在返回值前加上<T>標(biāo)識(shí)泛型;
5、其它用法:Class<T>類傳遞及泛型數(shù)組
(1)、使用Class<T>傳遞泛型類Class對象
有時(shí),我們會(huì)遇到一個(gè)情況,比如,我們在使用JSON解析字符串的時(shí)候,代碼一般是這樣的
public static List<SuccessModel> parseArray(String response){
List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);
return modelList;
}
其中SuccessModel是自定義的解析類,代碼如下,其實(shí)大家不用管SuccessModel的定義,只考慮上面的那段代碼就行了。寫出來SuccessModel的代碼,只是不想大家感到迷惑,其實(shí),這里只是fastJson的基本用法而已。
這段代碼的意義就是根據(jù)SuccessModel解析出List<SuccessModel>的數(shù)組。
public class SuccessModel {
private boolean success;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
}
那現(xiàn)在,我們把下面這句組裝成一個(gè)泛型函數(shù)要怎么來做呢?
public static List<SuccessModel> parseArray(String response){
List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);
return modelList;
}
首先,我們應(yīng)該把SuccessModel單獨(dú)抽出來做為泛型變量,但parseArray()中用到的SuccessModel.class要怎么弄呢?
先來看代碼:
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
注意到,我們用的Class<T> object來傳遞類的class對象,即我們上面提到的SuccessModel.class。
這是因?yàn)镃lass<T>也是一泛型,它是傳來用來裝載類的class對象的,它的定義如下:
public final class Class<T> implements Serializable {
…………
}
通過Class<T>來加載泛型的Class對象的問題就講完了,下面來看看泛型數(shù)組的使用方法吧。
(2)、定義泛型數(shù)組
在寫程序時(shí),大家可能會(huì)遇到類似String[] list = new String[8];的需求,這里可以定義String數(shù)組,當(dāng)然我們也可以定義泛型數(shù)組,泛型數(shù)組的定義方法為 T[],與String[]是一致的,下面看看用法:
//定義
public static <T> T[] fun1(T...arg){ // 接收可變參數(shù)
return arg ; // 返回泛型數(shù)組
}
//使用
public static void main(String args[]){
Integer i[] = fun1(1,2,3,4,5,6) ;
Integer[] result = fun1(i) ;
}
我們先看看 定義時(shí)的代碼:
public static <T> T[] fun1(T...arg){ // 接收可變參數(shù)
return arg ; // 返回泛型數(shù)組
}
首先,定義了一個(gè)靜態(tài)函數(shù),然后定義返回值為T[],參數(shù)為接收的T類型的可變長參數(shù)。如果有同學(xué)對T...arg的用法不了解,可以去找下JAVA 可變長參數(shù)方面的知識(shí)。
由于可變長參數(shù)在輸入后,會(huì)保存在arg這個(gè)數(shù)組中,所以,我們直接把數(shù)組返回即可。
demo
關(guān)于泛型類的使用實(shí)例
import lombok.Data;
@Data
public class MultiObject<T> {
/**
* 成功狀態(tài)
*/
private boolean success;
/**
* 異常
*/
private Exception ex;
/**
* 數(shù)據(jù)
*/
private T obj;
public MultiObject() {
}
/**
* 注意:當(dāng)傳入的泛型是Boolean時(shí),就和第三個(gè)構(gòu)造函數(shù)沖突了。
*/
public MultiObject(boolean success) {
this.success = success;
}
public MultiObject(Exception ex) {
this.success = false;
this.ex = ex;
}
public MultiObject(T value) {
this.success = true;
this.obj = value;
}
}
關(guān)于泛型方法的使用實(shí)例
/**
* 將Json字符串信息轉(zhuǎn)換成對應(yīng)的Java對象
*
* @param json json字符串對象
* @param c 對應(yīng)的類型
*/
public static <T> T parseJsonToObj(String json, Class<T> c) {
try {
JSONObject jsonObject = JSONObject.parseObject(json);
return JSON.toJavaObject(jsonObject, c);
} catch (Exception e) {
LOG.error(e.getMessage());
}
return null;
}