泛型的定義及使用
1. 定義泛型:
Student<T>
2. 類中使用泛型
Student<T> 這個(gè) T 表示派生自object的任何類,比如 string、 intenger、 Double 等。 但是要注意的是, T 一定要
是派生自 object 類。
Class Student<T> {
private T name ;
public T getName(){
return name;
}
public void setName(T name){
this.name = name;
}
}
3. 使用泛型類
// 構(gòu)造實(shí)例
Student<String> s = new Student<String>();
s.setName("張三");
system.out.printin(s.getName());
4. 使用泛型的優(yōu)勢(shì)?
I. 為什么不用 object 代替泛型?就是因?yàn)閺?qiáng)制轉(zhuǎn)換會(huì)出現(xiàn)意想不到的錯(cuò)誤。
II. 在編譯期就能報(bào)錯(cuò)。
多泛型變量的定義及字母規(guī)范
1. 多泛型變量定義
上面我們只定義了一個(gè)泛型變量,那如果需要多個(gè)變量怎么辦? 只需要類似下面這樣就行了。
新加的變量和上例中的 T 用法一樣, 這樣的變量想加幾個(gè)加幾個(gè)。只要用 "," 隔開就行。
class Student<T,E>{
Private T name;
Private E age;
......
}
class Student<T,E,U,K,V>{}
2. 字母規(guī)范
指定泛型的可以是任意一個(gè)大寫字母,咩有特定的含義。 但是為了提高可讀性,大家還是用比較有意義的字母為好。
- E, Element 常用在 collection 中,如: List<E>, iterator<E>, Set<E>
- K,V key-value 鍵值對(duì)
- N, Number 數(shù)字
- T, Type 類型,如 string integer 等
泛型接口的定義和使用
- 非泛型類
- 泛型類
泛型函數(shù)的定義和使用
不管是靜態(tài)還是非靜態(tài)函數(shù),不管是有沒有返回值,都在返回值類型前加符號(hào)<T> ,以用來(lái)標(biāo)識(shí)泛型。
- 靜態(tài)泛型函數(shù)
- 非靜態(tài)泛型函數(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());
}
// 有返回值的泛型函數(shù)
public static <T> List<T> parseArray(String response,Class<T> object){
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}
}
其他用法:Class<T> 類傳遞及泛型數(shù)組
Class<T> 其實(shí)也是一種泛型,用來(lái)裝載 class 對(duì)象的。
public final class Class<T> implements Serializable {
…………
}
泛型高級(jí)知識(shí): 類型綁定和通配符
泛型的類型綁定: extends
- <T extends BoundingType> 就是給泛型參數(shù)加一個(gè)界限。
- 此時(shí)的 extends 不等同于 繼承。和繼承沒有任何聯(lián)系。
- BoundingType 可以是類,也可以是接口。 T 代表的類型是被包含的意思。具有 BoundingType的功能。
- 能提前調(diào)用 T 的父類或父接口中的方法。
- 綁定接口
public interface Comparable<T> { boolean compareto(T i); } public class StringCompare implements Comparable<StringCompare> { String mStr; public StringCompare(String string) { this.mStr = string; } @Override public boolean compareto(StringCompare i) { if (mStr == null) { throw new NullPointerException(); } if (mStr.length() > i.mStr.length()) { return true; } return false; } } public static <T extends Comparable> T min(T... a) { T smallest = a[0]; for (T item : a) { if (smallest.compareto(item)) { smallest = item; } } } // 調(diào)用時(shí)候 // 可以看出類型綁定有兩個(gè)作用: // 1、對(duì)填充的泛型加以限定 // 2、使用泛型變量T時(shí),可以使用BoundingType內(nèi)部的函數(shù)。 public static <T extends Comparable> T min(T... a) { T smallest = a[0]; for (T item : a) { if (smallest.compareto(item)) { smallest = item; } } return smallest; } StringCompare result = min(new StringCompare("1"),new StringCompare("123")); Log.e("xyd","result = " + result.mStr); - 綁定類
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(); } class Banana extends Fruit{ public Banana() { setName("banana"); } } class Apple extends Fruit{ public Apple() { setName("apple"); } } // 最后使用; Log.e("xyd","FruitName = " + getFruitName(new Banana())); Log.e("xyd","FruitName = " + getFruitName(new Apple())); - 綁定多個(gè)綁定,用 & 連接
public static <T extends Fruit&Serializable> String getFruitName(T t){ ... } 加深難度,如果有多個(gè)泛型,每個(gè)泛型都帶綁定? public static <T extends Comparable & Serializable, U extends Runnable> T foo(T a, U b){ ... }
通配符
無(wú)邊界通配符
- 無(wú)邊界通配符初識(shí)
有沒有辦法,只生成一個(gè)變量,可以將不同的實(shí)例賦值給它呢?首先定義定義一個(gè)泛型類: class Point<T>{ private T x; private T y; public Point() { } public Point(T x, T y) { this.x = x; this.y = 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<?> point; point = new Point<Integer>(3,3); point = new Point<Float>(2.3f,5.6f); point = new Point<Double>(6.3d,46.5d); point = new Point<Long>(10l,58l); 這里的 ? 就是無(wú)邊界通配符。就是一個(gè)任意的類,一個(gè)未知的符號(hào)。 point = new Point<String>("",""); point = new Point<Object>(); 不光能將 T 填充為數(shù)值類,任意的 Object 的子類都可以匹配給通配符。 - 通配符 ? 和 T 的區(qū)別
他們倆沒有任何聯(lián)系; 泛型變量T不能在代碼中用于創(chuàng)建變量,只能在類、接口、函數(shù)中聲明后使用。 無(wú)界通配符只能用于填充泛型變量T,表示通配任何類型。它是用來(lái)填充 T 的,只是填充方式的一種??! // 無(wú)邊界通配符填充 Box<?> box; // 其他類型填充 Box<String> stringBox; - 通配符只能用于填充泛型變量T,不能用于定義變量
只能用來(lái)填充變量T的位置,不能用于定義變量。通配符的使用位置只有: Box<?> box; box = new Box<String>(); 而不能用于定義變量: box = new Box<?>(); // 錯(cuò)誤 ? x; // 錯(cuò)誤
通配符 ? 的 extends 綁定 // 上邊界限定通配符
通配符?可以代表任意類型,但跟泛型一樣,如果不加以限定,在后期的使用中編譯器可能不會(huì)報(bào)錯(cuò)。所以我們同樣要對(duì)通配符加以限定。
綁定的形式,同樣是通過extends 關(guān)鍵字,意義和使用方法都和泛型變量一致。
為了保證泛型類有意義,需要對(duì)泛型類的參數(shù)做限制,不能超出限制之外。
如有的參數(shù)只有是數(shù)字類型才有意義。
Point<? extends Number> point;
point = new Point<Number>();
point = new Point<Float>(3.4f,4.3f);
point = new Point<Double>(3.4d,4.3d);
point = new Point<Long>(3.4l,4.3l);
point = new Point<String>("",""); // 報(bào)錯(cuò)
point = new Point<Object>(); // 報(bào)錯(cuò)
//----
編譯時(shí)候,最后兩行會(huì)報(bào)錯(cuò)。
new Point<Number>(); 不會(huì)報(bào)錯(cuò),說(shuō)明無(wú)邊界通配符的extends綁定包括邊界自身。
無(wú)邊界通配符只是泛型T的填充方式,給他加上限定,只是限定了賦值給它的實(shí)例類型。
如果想從根本上解決亂填充Point的問題,需要從 Point 泛型類定義時(shí)候就加上 <T extends Number>
注意: 利用<? extends Number> 定義的變量,只可取其中的值,不可修改。
Point<? extends Number> point;
point = new Point<Integer>(3,33);
Number Integer_x = point.getX();
point.setX(new Integer(222)); // 報(bào)錯(cuò)
// 為什么會(huì)報(bào)錯(cuò)???
因?yàn)?Point 的類型是由 Point<? extends Number> 決定的,并不會(huì)因?yàn)?point = new Point<Integer>(3,33)而改變類型。
即便 point = new Point<Integer>(3,3) 之后, point 的類型依然是 Point<? extends Number>(),
即派生自 Number 的未知類型?。?!
怎么理解? 如果在 point = new Point<Integer>(3,3) 之后, point 就變成了 Poin<Integer> 類,那后面
point = new Point<Long>(2l,22l); 肯定會(huì)因?yàn)轭愋筒黄ヅ涠鴪?bào)編譯錯(cuò)誤了。正因?yàn)?point 的類型始終是
Point<? extends Number> 因此能繼續(xù)被各種類型的實(shí)例賦值。
繼續(xù)正題,為什么會(huì)報(bào)錯(cuò)?
point 的類型為 Point<? extends Number> , 那就是說(shuō)填充 point 泛型變量 T 的是 <? extends Number>,
這是一個(gè)什么類型? 未知類型。怎么可能用一個(gè)未知類型來(lái)給設(shè)置內(nèi)部值! 這是不合理的。
但是取值時(shí), 正由于泛型變量T被填充為<? extends Number> 所以編譯器能確定 T 肯定是 Number 的子類。編譯器就會(huì)用 Number 來(lái)填充 T。
也就是說(shuō),編譯器,只要能確定通配符類型,就會(huì)允許,如果無(wú)法確定通配符的類型,就會(huì)報(bào)錯(cuò)。
通配符 ?的 supper 綁定 // 下邊界限定通配符
總結(jié):
通配符的使用可以對(duì)泛型參數(shù)做出某些限制,使的代碼更安全,對(duì)于上邊界和下邊界限定的通配符總結(jié)如下:
- 使用 List<? extends C> list 這種形式,表示 list 可以引用一個(gè) ArrayList ( 或者其它 List 的 子類 ) 的對(duì)象,這個(gè)對(duì)象包含的元素類型是 C 的子類型 ( 包含 C 本身)的一種。
- 使用 List<? super C> list 這種形式,表示 list 可以引用一個(gè) ArrayList ( 或者其它 List 的 子類 ) 的對(duì)象,這個(gè)對(duì)象包含的元素就類型是 C 的超類型 ( 包含 C 本身 ) 的一種。
參考資料:
Java 泛型總結(jié)(一):基本用法與類型擦除
Java 泛型總結(jié)(二):泛型與數(shù)組
Java 泛型總結(jié)(三):通配符的使用
夯實(shí)JAVA基本之一 —— 泛型詳解(1):基本使用
夯實(shí)JAVA基本之一——泛型詳解(2):高級(jí)進(jìn)階
什么是協(xié)變?為什么數(shù)組是支持協(xié)變的?為什么泛型不支持協(xié)變?
參考知乎 - 胖胖 的答案