一.泛型是什么
?? 避免強轉、在編譯時檢查出來傳進去的類型對不對、利于程序擴展。
二.各種泛型定義及使用
1.泛型類型定義及使用
//定義
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());
??(1)定義泛型:Point<T>
首先,大家可以看到Point<T>,即在類名后面加一個尖括號,括號里是一個大寫字母。這里寫的是T,其實這個字母可以是任何大寫字母,大家這里先記著,可以是任何大寫字母,意義是相同的。
??(2)類中使用泛型
這個T表示派生自Object類的任何類,比如String,Integer,Double等等。這里要注意的是,T一定是派生于Object類的。為方便起見,大家可以在這里把T當成String,即String在類中怎么用,那T在類中就可以怎么用!
??(3)使用泛型類
在泛型類構造的時候,在類名后面加上<想傳入的類型>
2、多泛型變量定義及字母規范
??(1)多泛型變量定義
<T,U,A,B,C....>相加幾個就加幾個
??(2)、字母規范
任意一個大寫字母都可以。他們的意義是完全相同的,但為了提高可讀性,大家還是用有意義的字母比較好,一般來講,在不同的情境下使用的字母意義如下:
- E — Element,常用在java Collection里,如:List<E>,Iterator<E>,Set<E>
- K,V — Key,Value,代表Map的鍵值對
- N — Number,數字
- T — Type,類型,如String,Integer等等
3、泛型接口定義及使用
泛型接口和泛型類一樣:
public interface Infor<T> {
T getVar();
}
但是在使用時候有以下兩種情況;
- 使用方法一:非泛型類
class InforImpl implements Infor<String>{
@Override
public String getVar() {
return null;
}
}
??先要認清楚一點InforImpl 不是一個泛型類,因為它后面沒有<T>!!!這里我們將Infor<>填充了String類型,getVar()的返回值也變成了String。
- 使用方法二:泛型類
class InforImpl<T> implements Infor<T>{
@Override
public T getVar() {
return null;
}
}
??我們構造了一個泛型類InforImpl<T>,然后把泛型變量T 傳給了Infor<T>,說明接口和泛型類使用的是同一個泛型變量。在使用的過程中:
這里傳入想要的類型<String>
InforImpl<String> infor = new InforImpl();
String var = infor.getVar();
4、泛型函數定義及使用
上面提到了類和接口的泛型使用,下面看看怎么單獨在一個函數中使用泛型:
public class StaticFans {
public static <T> void staticMethod(T t) {
System.out.println(t.toString());
}
public <T> void method(T t) {
System.out.println(t.toString());
}
}
??上面的寫法和平時的方法差不多,唯一的區別就是在返回值(Void)前面加上<T>來表示泛型變量.
使用方法如下:
   可以像普通方法一樣,直接傳值,任何值都可以(但必須是派生自Object類的類型,比如String,Integer等),函數會在內部根據傳進去的參數來識別當前T的類別
StaticFans.staticMethod("String類型");
StaticFans.staticMethod(666666);//整型
先創建類的實例,然后調用泛型函數。
new StaticFans().method("String類型");
new StaticFans().method(666666);
- 進階:返回值中存在泛型
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
函數的返回值是List<T>類型
5、其它用法:Class<T>類傳遞
有時,我們在Gson解析對象時,代碼一般這樣寫
public static List<SuccessModel> parseArray(String response){
List<SuccessModel> modelList = JSON.parseArray(response, SuccessModel.class);
return modelList;
}
??如果只有一兩個類還行,如果需要很多,這么寫豈不是很麻煩,parseArray里面我們需要一個SuccessModel.class,這時候就要用上Class<T>,使用Class<T>傳遞泛型類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。
--------------------- 下面是進階部分-------------------
-------被溫水煮慣了,夢想的東西總是不敢于嘗試,失敗了又怎樣,最多從頭來過。--------
一 、類型綁定:extends
??有時候,你會希望泛型類只能是某一部分,,比如操作數據的時候,你希望是Number或者是它的子類,這個想法就是給泛型參數增加一個范圍
<T extends BoundingType>
??T 表示BoundingType的子類型,T和BoundingType都可以是類,也可以是接口。extends表示子類型,不等同于繼承。注意:::這里的extends不是繼承里的extends,兩者根本沒有任何關聯。
先設置一個基類
class Fruit {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
再寫個泛型函數來取名字
public static <T extends Fruit> String getFruitName(T t){
return t.getName();
}
??這里泛型函數的用法就出來了,由于我們已經水果都會繼承Fruit類,所以我們利用<T extends Fruit>就可以限定填充的變量必須派生字Fruit的子類。一來,在T中,我們可以利用Fruit中方法和函數;二來,如果用戶填充進去的類沒有派生自Fruit,那編譯器就會報錯。
然后,我們新建兩個類,派生自Fruit,并填充進去它們自己的名字:
class Banana extends Fruit{
public Banana(){
setName("bababa");
}
}
class Apple extends Fruit{
public Apple(){
setName("apple");
}
}
最后調用:
String name_1 = getFruitName(new Banana());
String name_2 = getFruitName(new Apple());
Log.d(TAG,name_1);
Log.d(TAG,name_2);
二、通配符
1.引入
public class Point<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;
}
}
使用情況:
Point<Integer> integerPoint = new Point<Integer>(3,3);
…………
Point<Float> floatPoint = new Point<Float>(4.3f,4.3f);
…………
Point<Double> doublePoint = new Point<Double>(4.3d,4.90d);
…………
Point<Long> longPoint = new Point<Long>(12l,23l);
在這段代碼中,我們使用Point<T>生成了四個實例:integerPoint,floatPoint,doublePoint和longPoint;
??在這里,我們生成四個實例,就得想四個名字。如果我們想生成十個不同類型的實例呢?那不得想十個名字。
??光想名字就是個事,(其實我并不覺得想名字是個什么大事…… T _ T ,沒辦法,想不出更好的例子了…… )
??那有沒有一種方法,生成一個變量,可以將不同類型的實例賦值給它嗎?---?來了
2.無邊界通配符: ?
如果我們用?,可以這樣實現
Point<?> point;
point = new Point<Integer>(3,3);
point = new Point<Float>(4.3f,4.3f);
point = new Point<Double>(4.3d,4.90d);
point = new Point<Long>(12l,23l);
? 就是無邊界通配符,可以代表任意的類
?和T的區別
?和T 沒有任何的聯系?。?!
?和T 沒有任何的聯系?。。?br>
?和T 沒有任何的聯系?。。?br>
??泛型T不能在代碼用于創建變量,只能在類、接口、函數中聲明以后,才能使用。
而無界通配符?只能用于填充泛型變量T,它是用來填充T的!??!只是填充方式的一種。
3、通配符?的extends綁定
從上面我們可以知道通配符?可以代表任意類型,但跟泛型一樣,如果不加以限定,在后期的使用中編譯器可能不會報錯。所以我們同樣,要對?加以限定。
綁定的形式,同樣是通過extends關鍵字,意義和使用方法都用泛型變量一致。
同樣,以我們上面的Point<T>泛型類為例,因為Point在實例意義中,其中的值是數值才有意義,所以將泛型變量T填充為Object類型、String類型等都是不正確的。
所以我們要對Point<?> point加以限定:只有數值類型才能賦值給point;
我們把代碼改成下面的方式:
Point<? extends Number> point;
point=new Point<Number>();
point=new Point<Integer>();
point=new Point<String>();//有錯誤
point=new Point<Object>();//有錯誤
??我們給通配符加上限定:Point<? extends Number> point;此時,最后兩行,當T填充為String和Object,point就會報錯。雖然指派的是派生自Number的任意類型, new Point<Number>();也是可以成功賦值的,說明包括邊界本身。
??再強調一遍:無邊界通配符只是泛型T的填充方式,給它加上限定,只是限定了賦值給它的實體類。
注意:利用<? extends Number>定義的變量,只可取其中的值,不可修改
Point<? extends Number> point;
point = new Point<Integer>(3, 3);
Number i = point.getX();
point.setX(1);//這里會報錯
為什么getX()可以,而setX()不行?
??首先,point類型是由Point<? extends Number>決定的,并不會因為 point = new Point<Integer>(3, 3)而改變類型,假設如果point類型隨著 point = new Point<Integer>(3, 3)改變,那么point = new Point<Long>(12l,23l)就會報錯,正因為point類型始終是<? extends Number>,因為能被各種實例賦值。打個比方:Point類就好比學生,point是學生1,此時point類型還是學生,而不是學生1.
??然后,正因為point類型<? extends Number>,這是一個什么類型?這其實是一個未知類型Integer、long、double。。。怎么可能給一個未知類型去賦值?顯然是不合理的。
??最后,當我們去getX()的時候,取得類型可能是Integer、long。。雖然類型不確定,但它一定是Number的子類,也就是說,編譯器只要能確定通配符的類型,就會允許,如果無法確定通配符的類型,就會報錯。
4、通配符?的super綁定
?? 如果說< ? extends XXX> 指的是填充派生于XXX的子類,那么<? super XXX> 指的是任意XXX的父類。
class CEO extends Manager {
}
class Manager extends Employee {
}
class Employee {
}
List<? super Manager> list;
list = new ArrayList<Employee>();
list = new ArrayList<Manager>();
list = new ArrayList<CEO>();//這句話報錯
因為CEO已經不是Manager的父類了,所以編譯會報錯。super的關鍵字也是包括邊界的。
super通配符實例內容:能存不能取
List<? super Manager> list=new ArrayList<>();
list.add(new CEO());
list.add(new Manager());
list.add(new Employee());//這句話報錯
為什么前兩個行,最后一個不行。這里可以這么理解:父類的引用可以指向子類對象。假設?是Manager,CEO是Manager的子類,最后都可以強轉成Manager。但是Manager是Employee 的子類,所以Employee 不行。這就好比我聲明的是一個Dog對象,我們在add的時候不能new 一個Animal進去。
?? 我們再來看看?。?/p>
Object object=list.get(0);
Employee employee=list.get(0); //報錯
Manager manager=list.get(0);//報錯
CEO ceo=list.get(0);//報錯
我們填充的是Manager的父類,假設是Employee 或者是Object,取得的值肯定是Object的子類,所以第一個肯定沒有問題。我們無法判斷我們取得值是什么類型,所以都是報錯的。但取出一個Object也沒有什么意思,所以,我們認為super能存不能取。
5、通配符?總結
- 如果你想從一個數據類型里獲取數據,使用 ? extends 通配符(能取不能存)
- 如果你想把對象寫入一個數據結構里,使用 ? super 通配符(能存不能?。?/li>
- 如果你既想存,又想取,那就別用通配符。