Java 泛型詳解
在日常的開發中,我們會看到別人的框架很多地方會使用到泛型,泛型是Java SE 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。這種參數類型可以用在類、接口和方法的創建中,分別稱為泛型類、泛型接口、泛型方法。泛型的類型參數隻能是類類型(包括自定義類),不能是簡單類型。本篇博客我們就來詳細解析一下泛型的知識。
泛型類定義及使用
使用泛型有什麼好處呢?首先我們先看一個例子,假設我們有兩個類,代碼如下:
#StringClass
public class StringClass {
private String x ;
private String y ;
public String getY() {
return y;
}
public void setY(String y) {
this.y = y;
}
public String getX() {
return x;
}
public void setX(String x) {
this.x = x;
}
}
#IntClass
public class IntClass {
private int x ;
private int y ;
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
}
觀察上麵兩個類StringClass 和IntClass,他們除了變量類型不一樣,一個是String一個是int以外,其它並沒有什麼區別!那我們能不能合並成一個呢?通過泛型就可以解決,首先看一下泛型的類是怎麼定義的:
public class ObjClass<T> {
private T x ;
private T y ;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
那麼這時候上麵的兩個類就可以通過泛型的設置,相應生成
ObjClass<String> stringClass = new ObjClass<String>();
stringClass.setX("haha");
ObjClass<Integer> intClass = new ObjClass<Integer>();
intClass.setX(100);
Log.d("yyy", "stringClass:" + stringClass.getX() + ",intClass:" + intClass.getX());
從結果中可以看到,我們通過泛型實現了開篇中StringClass類和IntClass類的效果。
接下來介紹泛型如何定義及使用:
1.首先需要定義泛型:ObjClass
ObjClass ,即在類名後麵加一個尖括號,括號裏是一個大寫字母。這裏寫的是T,其實這個字母可以是任何大寫字母,無論使用哪個字母,意義都是相同的。如果你想學習Java可以來這個群,首先是二二零,中間是一四二,最後是九零六,裏麵有大量的學習資料可以下載
2.在類中使用泛型
這個T表示派生自Object類的任何類,比如String,Integer,Double等等。這裏要注意的是,T一定是派生於Object類的。
private T x ;
private T y ;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
3.使用泛型類
泛型類的使用代碼如下:
ObjClass<String> stringClass = new ObjClass<String>();
stringClass.setX("haha");
ObjClass<Integer> intClass = new ObjClass<Integer>();
intClass.setX(100);
首先,需要構造一個實例:
ObjClass<String> stringClass = new ObjClass<String>();
泛型類的構造則需要在類名後添加上,即一對尖括號,中間寫上要傳入的類型。
因為我們構造時,是這樣的:ObjClass,所以在使用的時候也要在ObjClass後加上類型來定義T代表的意義。
尖括號中,你傳進去的是什麼,T就代表什麼類型。這就是泛型的最大作用,我們隻需要考慮邏輯實現,就能拿給各種類來用。
多泛型變量定義
1.多泛型變量定義
我們不止可以在類中設置一個泛型變量T,還可以聲明多個泛型變量,寫法如下:
public class ObjClass<T,U>
也就是在原來的T後麵用逗號隔開,寫上其它的任意大寫字母即可,如果還有多個,依然使用逗號分隔開即可,則我們前麵定義的泛型類就會變成下麵這樣:
public class ObjClass<T,U> {
private T x ;
private U y ;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public U getY() {
return y;
}
public void setY(U y) {
this.y = y;
}
}
ObjClass<String,Integer> stringClass = new ObjClass<String,Integer>();
stringClass.setX("haha");
stringClass.setY(100);
從上麵的代碼中,可以明顯看出,就是在新添加的泛型變量U用法與T是一樣的。
2.泛型的字母規範
雖然在類中聲明泛型任意字母都可以,但為了可讀性,最好遵循以下的規範:
E — Element,常用在java Collection裏,如: List<E>,Iterator<E>,Set<E>
K,V — Key,Value,代表Map的鍵值對
N — Number,數字
T — Type,類型,如String,Integer等等
泛型接口定義及使用
在接口上定義泛型與在類中定義泛型是一樣的,代碼如下:
interface MsgClass<T> {
public T getMsg() ;
public void setMsg(T x);
}
我們可以利用泛型類來構造填充泛型接口
public class Message<T,U> implements MsgClass<T>{
private T msg;
@Override
public T getMsg() {
return msg;
}
@Override
public void setMsg(T msg) {
this.msg = msg;
}
}
在這個類中,我們構造了一個泛型類Message,然後把泛型變量T傳給了MsgClass,這說明接口和泛型類使用的都是同一個泛型變量。
我們還可以構造一個多個泛型變量的類,並繼承自MsgClass接口:
public class Message<T,U> implements MsgClass<T>{
private U name;
private T msg;
@Override
public T getMsg() {
return msg;
}
@Override
public void setMsg(T msg) {
this.msg = msg;
}
public U getName() {
return name;
}
public void setName(U name) {
this.name = name;
}
}
泛型函數定義及使用
我們不但可以在類聲明中使用泛型,還可以在函數聲明中也使用泛型,使用如下:
public class ObjClass {
//靜態函數
public static <T> void StaticMethod(T a) {
}
//普通函數
public <T> void OrgnicMethod(T a) {
}
}
上麵分別是靜態泛型函數和常規泛型函數的定義方法,與以往方法的唯一不同點就是在返回值前加上來表示泛型變量。
無論哪種泛型方法都有兩種使用方法:
//靜態方法
ObjClass.StaticMethod("adfdsa");//使用方法一
ObjClass.<String>StaticMethod("adfdsa");//使用方法二
//常規方法
ObjClass objClass = new ObjClass();
objClass.OrgnicMethod(new Integer(111));//使用方法一
objClass.<Integer>OrgnicMethod(new Integer(111));//使用方法二
方法一,隱式傳遞了T的類型,這種隱式的傳遞方式,代碼不利於閱讀和維護。因為從外觀根本看不出來你調用的是一個泛型函數。
方法二,例如上麵例子中,將T賦值為Integer類型,這樣OrgnicMethod(T a)傳遞過來的參數如果不是Integer那麼編譯器就會報錯。
當然泛型函數的返回值也可以使用泛型表示:
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
函數返回值是List類型。和void的泛型函數不同,有返回值的泛型函數要在函數定義的中在返回值前加上標識泛型;還要說明的是,上麵中,使用Class傳遞泛型類Class對象
泛型數組
泛型同樣可以用來定義在數組上
//定義
public static <T> T[] fun1(T...msg){ // 接收可變參數
return msg ; // 返回泛型數組
}
//使用
public static void main(String args[]){
Integer i[] = fun1(8,9,8,44) ;
Integer[] result = fun1(i) ;
}
定義了一個靜態函數,然後定義返回值為T[],參數為接收的T類型的可變長參數。
泛型的通配符
在開發中對象的引用傳遞(向上向下傳遞)是最常見的,但是,在泛型的操作中,在進行引用傳遞的時候泛型類型必須匹配才可以傳遞,否則不能傳遞。
例如,如下沒有進行泛型類型匹配,一個是String,一個是Object類型。
class Info<T>{
private T var ; // 定義泛型變量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){
return this.var.toString() ;
}
};
public class demo1 {
public static void main(String args[]) {
// 使用String為泛型類型
Info<String> i = new Info<String>();
i.setVar("ABCD");
//把String泛型類型的i對象傳遞給Object泛型類型的temp。
fun(i);
}
// 接收Object泛型類型的Info對象
public static void fun(Info<Object> temp) {
System.out.println("內容:" + temp);
}
}
編譯發生錯誤。
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The method fun(Info<Object>) in the type demo1 is not applicable for the arguments (Info<String>)
at Thread1.demo1.main(demo1.java:18)
泛型對象進行引用傳遞的時候,類型必須一致,如果非要傳遞,則可以將fun方法中Info參數的泛型取消掉(變成 void fun(Info temp))。、
以上確實改進了功能,但是似乎不是很妥當,畢竟之前指定過泛型。
以上程序在fun()方法中使用 "Info<?>" 的代碼形式,表示可以使用任意的泛型類型對象,這樣的話fun()方法定義就合理了,但是使用以上方法也有需要注意的地方,
即:如果使用“?“接收泛型對象的時候,則不能設置被泛型指定的內容。
class Info<T>{
private T var ;
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){
return this.var.toString() ;
}
};
public class GenericsDemo{
public static void main(String args[]){
Info<String> i = new Info<String>() ;
i.setVar("ABCD") ;
fun(i) ;
}
public static void fun(Info<?> temp){
System.out.println("內容:" + temp) ;
}
};
如果使用”?“意味著可以接收任意的內容,但是此內容無法直接使得用”?“修飾的泛型的對象進行修改。如下就會出問題:
class Info<T>{
private T var ;
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){
return this.var.toString() ;
}
};
public class demo1{
public static void main(String args[]){
Info<?> i = new Info<String>() ;
i.setVar("ABCD") ;
}
};
運行結果:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The method setVar(capture#1-of ?) in the type Info<capture#1-of ?> is not applicable for the arguments (String)
at Thread1.demo1.main(demo1.java:17)
在使用”?“隻能接收,不能修改。
泛型的上限
class Info<T>{
private T var ;
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){
return this.var.toString() ;
}
};
public class GenericsDemo{
public static void main(String args[]){
Info<Integer> i1 = new Info<Integer>() ;
Info<Float> i2 = new Info<Float>() ;
i1.setVar(30) ;
i2.setVar(30.1f) ;
fun(i1) ;
fun(i2) ;
}
public static void fun(Info<? extends Number> temp){ // 隻能接收Number及其Number的子類
System.out.print(temp + "、") ;
}
};
運行成功。但是,如果傳入的泛型類型為String的話就不行,因為String不是Number子類。
在類中使用泛型上限。
class Info<T extends Number>{ // 此處泛型隻能是數字類型
private T var ;
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){
return this.var.toString() ;
}
};
public class demo1{
public static void main(String args[]){
Info<Integer> i1 = new Info<Integer>() ; // 聲明Integer的泛型對象
}
};
如果在使用Info的時候設置成String類型,則編譯的時候將會出現錯誤(String不是Number子類)
注意:利用 <? extends Number> 定義的變量,隻可取其中的值,不可修改
原因如下:
因為Info的類型為 Info
泛型的下限
<? super XXX> 表示填充為任意XXX的父類
class Info<T>{
private T var ;
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){
return this.var.toString() ;
}
};
public class GenericsDemo21{
public static void main(String args[]){
Info<String> i1 = new Info<String>() ; //
Info<Object> i2 = new Info<Object>() ; //
i1.setVar("hello") ;
i2.setVar(new Object()) ;
fun(i1) ;
fun(i2) ;
}
public static void fun(Info<? super String> temp){ // 隻能接收String或Object類型的泛型,String類的父類隻有Object類
System.out.print(temp + "、") ;
}
};
Object類和String類都是String的父類,所有運行成功,但是如果此時用Integer則會出錯,因為integer並不是String父類。
注意:使用super通配符:能存不能取
如何理解呢?假設有3個類,繼承關係如下:
class CEO extends Manager {
}
class Manager extends Employee {
}
class Employee {
}
然後書寫如下代碼:
List<? super Manager> list;
list = new ArrayList<Employee>();
//存
list.add(new Employee()); //編譯錯誤
list.add(new Manager());
list.add(new CEO());
為什麼而list.add(new Employee());是錯誤的?
因為list裏item的類型是
List<Employee> list = new ArrayList<Employee>();
list.add(new Manager());
list.add(new CEO());
在這裏,正因為Manager和CEO都是Employee的子類,在傳進去list.add()後,會被強製轉換為Employee!
現在回過頭來看這個:
List<? super Manager> list;
list = new ArrayList<Employee>();
//存
list.add(new Employee()); //編譯錯誤
list.add(new Manager());
list.add(new CEO());
編譯器無法確定 <? super Manager> 的具體類型,但唯一可以確定的是Manager()、CEO()肯定是 <? super Manager> 的子類,所以肯定是可以add進去的。但Employee不一定是 <? super Manager> 的子類,所以不能確定,不能確定的,肯定是不允許的,所以會報編譯錯誤。
最後強調一下, List<? super Manager> list取出的隻能是Object 類型,這裏雖然看起來是能取的,但取出來一個Object類型,是毫無意義的。所以才有了“super通配符:能存不能取”的結論。
總結1)使用?可以接收任意泛型對象。
2)泛型的上限:?extends 類型(能取不能存)。
3)泛型的下限:?super 類型? super 通配符(能存不能取)。
最後更新:2017-04-11 17:00:23