夯實JAVA基本之一 —— 泛型詳解(1):基本使用(轉)

1、引入
一、泛型是什麼
首先告訴你們ArrayList就是泛型。那ArrayList能完成哪些想不到的功能呢?先看看下面這段代碼:
ArrayList<String> strList = new ArrayList<String>();
ArrayList<Integer> intList = new ArrayList<Integer>();
ArrayList<Double> doubleList = new ArrayList<Double>();
你們對ArrayList很熟悉,這裏構造了三個List,分別盛裝String、Integer和Double;這就是ArrayList的過人之處:即各類類型的變量均可以組裝成對應的List,而沒必要針對每一個類型分別實現一個構建ArrayList的類。這裏可能看不懂,開篇老是困難的,下面看看若是沒有泛型的話,咱們要怎麼作;
二、沒有泛型會怎樣
先看下面這段代碼:
咱們實現兩個可以設置點座標的類,分別設置Integer類型的點座標和Float類型的點座標:
//設置Integer類型的點座標
class IntegerPoint{
private Integer x ; // 表示X座標
private Integer y ; // 表示Y座標
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 ;
}
}
//設置Float類型的點座標
class FloatPoint{
private Float x ; // 表示X座標
private Float y ; // 表示Y座標
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 ;
}
}
那如今有個問題:你們有沒有發現,他們除了變量類型不同,一個是Integer一個是Float之外,其它並無什麼區別!那咱們能不能合併成一個呢?
答案是能夠的,由於Integer和Float都是派生自Object的,咱們用下面這段代碼代替:
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來代替全部的子類;
在使用的時候是這樣的:
ObjectPoint integerPoint = new ObjectPoint();
integerPoint.setX(new Integer(100));
Integer integerX=(Integer)integerPoint.getX();
在設置的時候,使用new Integer(100)來新建一個Integer
integerPoint.setX(new Integer(100));
而後在取值的時候,進行強制轉換:
Integer integerX=(Integer)integerPoint.getX();
因爲咱們設置的時候,是設置的Integer,因此在取值的時候,強制轉換是不會出錯的。
同理,FloatPoint的設置和取值也是相似的,代碼以下:
ObjectPoint floatPoint = new ObjectPoint();
floatPoint.setX(new Float(100.12f));
Float floatX = (Float)floatPoint.getX();
但問題來了:注意,注意,咱們這裏使用了強制轉換,咱們這裏setX()和getX()寫得很近,因此咱們明確的知道咱們傳進去的是Float類型,那若是咱們記錯了呢?
好比咱們改爲下面這樣,編譯時會報錯嗎:
ObjectPoint floatPoint = new ObjectPoint();
floatPoint.setX(new Float(100.12f));
String floatX = (String)floatPoint.getX();
不會!!!咱們問題的關鍵在於這句:
String floatX = (String)floatPoint.getX();
強制轉換時,會不會出錯。由於編譯器也不知道你傳進去的是什麼,而floatPoint.getX()返回的類型是Object,因此編譯時,將Object強轉成String是成立的。必然不會報錯。
而在運行時,則否則,在運行時,floatPoint實例中明明傳進去的是Float類型的變量,非要把它強轉成String類型,確定會報類型轉換錯誤的!
那有沒有一種辦法在編譯階段,即能合併成同一個,又能在編譯時檢查出來傳進去類型不對呢?固然,這就是泛型。
下面咱們將對泛型的寫法和用法作一一講解。
2、各類泛型定義及使用
一、泛型類定義及使用
咱們先看看泛型的類是怎麼定義的:
//定義
class Point<T>{// 此處能夠隨便寫標識符號
private T x ;
private T y ;
public void setX(T x){//做爲參數
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());
先看看運行結果:java

從結果中能夠看到,咱們實現了開篇中IntegerPoint類和FloatPoint類的效果。下面來看看泛型是怎麼定義及使用的吧。數組

(1)、定義泛型:Point<T>
首先,你們能夠看到Point<T>,即在類名後面加一個尖括號,括號裏是一個大寫字母。這裏寫的是T,其實這個字母能夠是任何大寫字母,你們這裏先記着,能夠是任何大寫字母,意義是相同的。
(2)類中使用泛型
這個T表示派生自Object類的任何類,好比String,Integer,Double等等。這裏要注意的是,T必定是派生於Object類的。爲方便起見,你們能夠在這裏把T當成String,即String在類中怎麼用,那T在類中就能夠怎麼用!因此下面的:定義變量,做爲返回值,做爲參數傳入的定義就很容易理解了。ide

//定義變量
private T x ;
//做爲返回值
public T getX(){
return x ;
}
//做爲參數
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());
首先,是構造一個實例:
Point<String> p = new Point<String>() ;
這裏與普通構造類實例的不一樣之點在於,普通類構造函數是這樣的:Point p = new Point() ;
而泛型類的構造則須要在類名後添加上<String>,即一對尖括號,中間寫上要傳入的類型。
由於咱們構造時,是這樣的:class Point<T>,因此在使用的時候也要在Point後加上類型來定義T表明的意義。
而後在getVar()和setVar()時就沒有什麼特殊的了,直接調用便可。
從上面的使用時,明顯能夠看出泛型的做用,在構造泛型類的實例的時候:
//IntegerPoint使用
Point<Integer> p = new Point<Integer>() ;
//FloatPoint使用
Point<Float> p = new Point<Float>() ;
尖括號中,你傳進去的是什麼,T就表明什麼類型。這就是泛型的最大做用,咱們只須要考慮邏輯實現,就能拿給各類類來用。
前面咱們提到ArrayList也是泛型,咱們順便它的實現:
public class ArrayList<E>{
…………
}
看到了吧,跟咱們的Point實現是同樣的,這也就是爲何ArrayList可以盛裝各類類型的主要緣由。
(4)使用泛型實現的優點
相比咱們開篇時使用Object的方式,有兩個優勢:
(1)、不用強制轉換
//使用Object做爲返回值,要強制轉換成指定類型
Float floatX = (Float)floatPoint.getX();
//使用泛型時,不用強制轉換,直接出來就是String
System.out.println(p.getVar());
(2)、在settVar()時若是傳入類型不對,編譯時會報錯函數

能夠看到,當咱們構造時使用的是String,而在setVar時,傳進去Integer類型時,就會報錯。而不是像Object實現方式同樣,在運行時纔會報強制轉換錯誤。this

二、多泛型變量定義及字母規範
(1)、多泛型變量定義
上在咱們只定義了一個泛型變量T,那若是咱們須要傳進去多個泛型要怎麼辦呢?
只須要在相似下面這樣就能夠了:
class MorePoint<T,U>{
}
也就是在原來的T後面用逗號隔開,寫上其它的任意大寫字母便可。想加幾個就加幾個,好比咱們想加五個泛型變量,那應該是這樣的:
class MorePoint<T,U,A,B,C>{
}
舉個粟子,咱們在Point上再另加一個字段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)、字母規範
在定義泛型類時,咱們已經提到用於指定泛型的變量是一個大寫字母:
class Point<T>{
…………
}
固然不是的!!!!任意一個大寫字母均可以。他們的意義是徹底相同的,但爲了提升可讀性,你們仍是用有意義的字母比較好,通常來說,在不一樣的情境下使用的字母意義以下:
 E — Element,經常使用在java Collection裏,如:List<E>,Iterator<E>,Set<E>
 K,V — Key,Value,表明Map的鍵值對
 N — Number,數字
 T — Type,類型,如String,Integer等等
若是這些還不夠用,那就本身隨便取吧,反正26個英文字母呢。
再重複一遍,使用哪一個字母是沒有特定意義的!只是爲了提升可讀性!!!!
三、泛型接口定義及使用
在接口上定義泛型與在類中定義泛型是同樣的,代碼以下:.net

interface Info<T>{ // 在接口上定義泛型
public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型類型
public void setVar(T x);
}
與泛型類的定義同樣,也是在接口名後加尖括號;
(1)、使用方法一:非泛型類
可是在使用的時候,就出現問題了,咱們先看看下面這個使用方法:對象

class InfoImpl implements Info<String>{ // 定義泛型接口的子類
private String var ; // 定義屬性
public InfoImpl(String var){ // 經過構造方法設置屬性內容
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>{
…………
}
要清楚的一點是InfoImpl不是一個泛型類!由於他類名後沒有<T>!
而後在在這裏咱們將Info<String>中的泛型變量T定義填充爲了String類型。因此在重寫時setVar()和getVar()時,IDE會也咱們直接生成String類型的重寫函數。
最後在使用時,沒什麼難度,傳進去String類型的字符串來構造InfoImpl實例,而後調用它的函數便可。
public class GenericsDemo24{
public void main(String arsg[]){
InfoImpl i = new InfoImpl("harvic");
System.out.println(i.getVar()) ;
}
};
(2)、使用方法二:泛型類
在方法一中,咱們在類中直接把Info<T>接口給填充好了,但咱們的類,是能夠構形成泛型類的,那咱們利用泛型類來構造填充泛型接口會是怎樣呢?blog

interface Info<T>{ // 在接口上定義泛型
public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型類型
public void setVar(T var);
}
class InfoImpl<T> implements Info<T>{ // 定義泛型接口的子類
private T var ; // 定義屬性
public InfoImpl(T var){ // 經過構造方法設置屬性內容
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()) ;
}
};
最關鍵的是構造泛型類的過程:
class InfoImpl<T> implements Info<T>{ // 定義泛型接口的子類
private T var ; // 定義屬性
public InfoImpl(T var){ // 經過構造方法設置屬性內容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
}
在這個類中,咱們構造了一個泛型類InfoImpl<T>,而後把泛型變量T傳給了Info<T>,這說明接口和泛型類使用的都是同一個泛型變量。
而後在使用時,就是構造一個泛型類的實例的過程,使用過程也不變。
public class GenericsDemo24{
public static void main(String arsg[]){
Info<String> i = new InfoImpl<String>("harvic");
System.out.println(i.getVar()) ;
}
};
使用泛型類來繼承泛型接口的做用就是讓用戶來定義接口所使用的變量類型,而不是像方法一那樣,在類中寫死。
那咱們稍微加深點難度,構造一個多個泛型變量的類,並繼承自Info接口:
class InfoImpl<T,K,U> implements Info<U>{ // 定義泛型接口的子類
private U var ;
private T x;
private K y;
public InfoImpl(U var){ // 經過構造方法設置屬性內容
this.setVar(var) ;
}
public void setVar(U var){
this.var = var ;
}
public U getVar(){
return this.var ;
}
}
在這個例子中,咱們在泛型類中定義三個泛型變量T,K,U而且把第三個泛型變量U用來填充接口Info。因此在這個例子中Info所使用的類型就是由U來決定的。
使用時是這樣的:泛型類的基本用法,再也不多講,代碼以下:
public class GenericsDemo24{
public void main(String arsg[]){
InfoImpl<Integer,Double,String> i = new InfoImpl<Integer,Double,String>("harvic");
System.out.println(i.getVar()) ;
}
}
四、泛型函數定義及使用
上面咱們講解了類和接口的泛型使用,下面咱們再說說,怎麼單獨在一個函數裏使用泛型。好比咱們在新建一個普通的類StaticFans,而後在其中定義了兩個泛型函數:
public class StaticFans {
//靜態函數
public static <T> void StaticMethod(T a){
Log.d("harvic","StaticMethod: "+a.toString());
}
//普通函數
public <T> void OtherMethod(T a){
Log.d("harvic","OtherMethod: "+a.toString());
}
}
上面分別是靜態泛型函數和常規泛型函數的定義方法,與以往方法的惟一不一樣點就是在返回值前加上<T>來表示泛型變量。其它沒什麼區別。
使用方法以下:
//靜態方法
StaticFans.StaticMethod("adfdsa");//使用方法一
StaticFans.<String>StaticMethod("adfdsa");//使用方法二

//常規方法
StaticFans staticFans = new StaticFans();
staticFans.OtherMethod(new Integer(123));//使用方法一
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
結果以下:繼承

首先,咱們看靜態泛型函數的使用方法:接口

StaticFans.StaticMethod("adfdsa");//使用方法一
StaticFans.<String>StaticMethod("adfdsa");//使用方法二
從結果中咱們能夠看到,這兩種方法的結果是徹底同樣的,但他們還有些區別的,區別以下:
方法一,能夠像普通方法同樣,直接傳值,任何值均可以(但必須是派生自Object類的類型,好比String,Integer等),函數會在內部根據傳進去的參數來識別當前T的類別。但儘可能不要使用這種隱式的傳遞方式,代碼不利於閱讀和維護。由於從外觀根本看不出來你調用的是一個泛型函數。
方法二,與方法一不一樣的地方在於,在調用方法前加了一個<String>來指定傳給<T>的值,若是加了這個<String>來指定參數的值的話,那StaticMethod()函數裏全部用到的T類型也就是強制指定了是String類型。這是咱們建議使用的方式。
一樣,常規泛型函數的使用也有這兩種方式:
StaticFans staticFans = new StaticFans();
staticFans.OtherMethod(new Integer(123));//使用方法一
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
能夠看到,與日常同樣,先建立類的實例,而後調用泛型函數。
方法一,隱式傳遞了T的類型,與上面同樣,不建議這麼作。
方法二,顯示將T賦值爲Integer類型,這樣OtherMethod(T a)傳遞過來的參數若是不是Integer那麼編譯器就會報錯。

進階:返回值中存在泛型
上面咱們的函數中,返回值都是void,但現實中不可能都是void,有時,咱們須要將泛型變量返回,好比下面這個函數:
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
函數返回值是List<T>類型。至於傳入參數Class<T> object的意義,咱們下面會講。這裏也就是想經過這個例子來告訴你們,泛型變量其實跟String,Integer,Double等等的類的使用上沒有任何區別,T只是一個符號,能夠表明String,Integer,Double……這些類的符號,在泛型函數使用時,直接把T看到String,Integer,Double……中的任一個來寫代碼就能夠了。惟一不一樣的是,要在函數定義的中在返回值前加上<T>標識泛型;
五、其它用法:Class<T>類傳遞及泛型數組
(1)、使用Class<T>傳遞泛型類Class對象
有時,咱們會遇到一個狀況,好比,咱們在使用JSON解析字符串的時候,代碼通常是這樣的
public static List<SuccessModel> parseArray(String response){
List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);
return modelList;
}
其中SuccessModel是自定義的解析類,代碼以下,其實你們不用管SuccessModel的定義,只考慮上面的那段代碼就好了。寫出來SuccessModel的代碼,只是不想你們感到迷惑,其實,這裏只是fastJson的基本用法而已。
這段代碼的意義就是根據SuccessModel解析出List<SuccessModel>的數組。
public class SuccessModel {
private boolean success;

public boolean isSuccess() {
return success;
}

public void setSuccess(boolean success) {
this.success = success;
}
}
那如今,咱們把下面這句組裝成一個泛型函數要怎麼來作呢?
public static List<SuccessModel> parseArray(String response){
List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);
return modelList;
}
首先,咱們應該把SuccessModel單獨抽出來作爲泛型變量,但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。
這是由於Class<T>也是一泛型,它是傳來用來裝載類的class對象的,它的定義以下:
public final class Class<T> implements Serializable {
…………
}
經過Class<T>來加載泛型的Class對象的問題就講完了,下面來看看泛型數組的使用方法吧。
(2)、定義泛型數組
在寫程序時,你們可能會遇到相似String[] list = new String[8];的需求,這裏能夠定義String數組,固然咱們也能夠定義泛型數組,泛型數組的定義方法爲 T[],與String[]是一致的,下面看看用法:
//定義
public static <T> T[] fun1(T...arg){ // 接收可變參數
return arg ; // 返回泛型數組
}
//使用
public static void main(String args[]){
Integer i[] = fun1(1,2,3,4,5,6) ;
Integer[] result = fun1(i) ;
}
咱們先看看 定義時的代碼:
public static <T> T[] fun1(T...arg){ // 接收可變參數
return arg ; // 返回泛型數組
}
首先,定義了一個靜態函數,而後定義返回值爲T[],參數爲接收的T類型的可變長參數。若是有同窗對T...arg的用法不瞭解,能夠去找下JAVA 可變長參數方面的知識。
因爲可變長參數在輸入後,會保存在arg這個數組中,因此,咱們直接把數組返回便可。

好了,這篇到這裏就結束了,這篇中主要講述了泛型在各方面的定義及用法,下篇,咱們將講述,有關泛型限定相關的知識。

若是本文有幫到你,記得加關注哦

本文所涉及源碼下載地址:http://download.csdn.net/detail/harvic880925/9275047

請你們尊重原創者版權,轉載請標明出處哦: http://blog.csdn.net/harvic880925/article/details/49872903 謝謝。

相關文章
相關標籤/搜索