1、為什麼我們需要泛型?
透過兩段程式碼就可以知道為什麼需要泛型
/***
* 沒有泛型的時候實現加法
*/public class NonGeneric {
//我們接到一個需求 兩個整數相加 ,然後我們就實現瞭如下程式碼
public int addInt(int x,int y){
return x+y;
}
//業務 發展了 需要 兩個浮點數 相加
public float addFloat(float x,float y){
return x+y;
}
//業務又又又發展了 需要兩個double相加
public double addDouble(double x,double y){
return x+y;
}
//就是應為引數型別不同 重寫這個方法 ,
//能不能只寫一段程式碼邏輯相同,傳入int folat double 都行呢!
//泛型的應用場景之1,可以傳入不同的資料(引數)型別,執行相同的程式碼
public void listDemo(){
/**
* list沒有用泛型的時候 ;add元素的時候 他們都變成了object型別的,
* get的時候 必須加一個強制
* 型別轉換,就因為要加強轉,所以如果add的是int 他就會報錯
* java.lang.ClassCastException:
* java.lang.Integer cannot be cast to java.lang.String
*/
List list =new ArrayList();
list.add("愛碼士趙Sir");
list.add("軒軒吖");
list.add(100);
for (int i=0;i<list.size();i++){
String name =(String)list.get(i);
System.out.println("name:"+name);
}
/***
* 泛型的應用場景之2,定義了泛型之後,
* 使用的過程中直接指定這個list所盛裝的型別只能放String,
* 這樣我們就能在編譯期,找到這個錯誤,
* 也避免了我們在使用的時候的強制型別轉換
*/
List<String> list2 =new ArrayList<>();
list2.add("愛碼士趙Sir");
list2.add("軒軒吖");// list2.add(100);
for (int i=0;i<list2.size();i++){
String name =list2.get(i);
System.out.println("name:"+name);
}
}
public static void main(String[] args) {
NonGeneric nonGeneric=new NonGeneric();
System.out.println(nonGeneric.addInt(1,2));
System.out.println(nonGeneric.addFloat(1f,2f));
System.out.println(nonGeneric.addDouble(1d,2d));
nonGeneric.listDemo();
}}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
- 適用於多種資料型別執行相同的程式碼
- 我們使用了泛型之後,在我們編碼的過程中就可以指定我們資料型別,而不需要進行強制型別轉換
- 如果我們插入了錯誤的資料型別,在編譯器就能發現這個錯誤,不至於我們到了執行期才拋這個異常
2、泛型類、泛型介面定義
泛型的定義:引數化的型別,在我們普通的方法中,傳入的int x,int y這是引數對吧,呼叫方法的時候傳進去一個實際的值,
引數型別:這個引數在定義的時候,這個引數型別本身,把它引數化,在實際 呼叫的時候,我們再告訴方法這個引數是什麼型別,這就是所謂的泛型
/***
* 泛型類的定義
* @param <T>
*/public class NormalGeneric <T>{
private T data;
public NormalGeneric() {
}
public NormalGeneric(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static void main(String[] args) {
NormalGeneric <String> normalGeneric=new NormalGeneric<>();
normalGeneric.setData("OK");
System.out.println(normalGeneric.getData());
}}12345678910111213141516171819202122232425262728
public interface Genertor<T> {
public T next();}/***
* 實現方法一,不指定泛型型別,泛型類實現泛型方法,實現類也是一個泛型類
* 在使用的時候和平常的泛型類沒有太大的差別
* @param <T>
*/public class ImplGenertor<T> implements Genertor<T>{
@Override
public T next() {
return null;
}}/***
* 實現方法二:指定泛型型別,實現方法的返回值 是指定的型別String,而實現方法一里面
* 返回值還是一個泛型
*/public class ImplGenertor2 implements Genertor<String> {
@Override
public String next() {
return null;
}}1234567891011121314151617181920212223242526
3、泛型方法辨析
泛型方法是獨立的,不一定非得再泛型類裡,泛型介面裡宣告
泛型方法的標誌就是返回值和許可權修飾符中間的
在普通類中可以使用泛型方法,在泛型類裡也是可以使用泛型方法的
和泛型類和介面一樣 都是在使用它的時候,才告訴編譯器我們的資料型別 像下邊的
一些高版本的JDK變的比較聰明,會自動推斷出型別比如下面的第二行程式碼
//泛型方法的標誌就是返回值和許可權修飾符中間的<T>
//在普通類中可以使用泛型方法,在泛型類裡也是可以使用泛型方法的
public <T> T genericMethod(T...a){
return a[a.length/2];
}
public void test(int x,int y){
System.out.println(x+y);
}
public static void main(String[] args) {
GenericMethod genericMethod=new GenericMethod();
genericMethod.test(1,1);
//和泛型類和介面一樣 都是在使用它的時候,才告訴編譯器我們的資料型別 像下邊的<String>
//一些高版本的JDK變的比較聰明,會自動推斷出型別比如下面的第二行程式碼
System.out.println(genericMethod.<String>genericMethod("小明","小紅","小綠"));
System.out.println(genericMethod.genericMethod(12,34,45));
}12345678910111213141516
public class GenericMethod2 {
public class Generic<T>{
private T key;
public Generic(T key) {
this.key = key;
}
//雖然在方法中使用了泛型,但是這並不是一個泛型方法
//這只是類中的一個普通成員方法,只不過他的返回值是在宣告泛型類已經宣告過的泛型
//所以在這個方法中才可以繼續使用T這個泛型
public T getKey(){
return key;
}
/***
* 這個方法顯然是有問題的,在編譯器會給我們提示這樣的錯誤訊息 cannot reslove symbol “E”
* 因為在類的宣告中並未宣告泛型E,所以在使用E做形參和返回值型別時,編譯器會無法識別
* @param key
* @return
*/// public E setKey(E key){// this.key=key;// }
}
/***
* 這也不是一個泛型方法,這就是一個普通的方法
* 只是使用了Generic<Number>這個泛型類做形參而已
* @param obj
*/
public void show(Generic<Number> obj){
}
/***
* 這個方法也是有問題的,編譯器會為我們提示錯誤資訊:unknown class E
* 雖然我們宣告瞭<T>,也表明了這是一個可以處理泛型的型別的泛型方法
* 但是隻宣告瞭泛型型別T,並未宣告泛型型別E,因此編譯器並不知道如何處理E這個型別
* @param ab
* @param <T>
* @return
*/// public <T> T show(E ab){//// }
/***
* 普通的類中沒有泛型的T 所以這個方法也是有問題的
* @param obj
*/// public void show(T obj){//// }
}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
- 不是所有寫在泛型方法裡的方法都叫泛型方法
- 泛型方法的標誌是<T>
public class GenericMethod3 {
static class Fruit{
@Override
public String toString() {
return "Fruit";
}
}
static class Apple extends Fruit{
@Override
public String toString() {
return "Apple";
}
}
static class Person{
@Override
public String toString() {
return "Person";
}
}
static class GenerateTest<T>{
public void show_1(T t){
System.out.println(t.toString());
}
/***
* 在泛型類中 宣告瞭一個泛型方法,使用泛型E,這種泛型可以為任意型別
* 型別可以與T相同,也可以不同
* 由於泛型方法在宣告的時候會宣告泛型<E>,因此即使在泛型類中並未宣告泛型
* 編譯器也能夠正確的識別泛型方法中識別的泛型
* @param t
* @param <E>
*/
public <E> void show_3(E t){
System.out.println(t.toString());
}
/***
* 在泛型類中宣告瞭一個泛型方法,使用泛型T
* 注意這個T是一種全新的型別,可以與泛型類中宣告的T不是同一種型別
* @param t
* @param <T>
*/
public <T> void show_2(T t){
System.out.println(t.toString());
}
public static void main(String[] args) {
Apple apple=new Apple();
Person person=new Person();
GenerateTest<Fruit> generateTest=new GenerateTest<>();
generateTest.show_1(apple);//apple 是fruit的子類 這沒有問題// generateTest.show_1(person);//傳person 肯定是不行的
generateTest.show_2(apple);
generateTest.show_2(person);//因為泛型方法 所以 完全可以把person傳進去,泛型 方法裡的引數型別,不管和泛型類,相同還是不相同,都得看成兩個不同的引數型別
generateTest.show_3(apple);
generateTest.show_3(person);//泛型類裡的泛型方法的泛型型別以泛型方法為準 泛型類的泛型型別隻影響泛型類的普通方法
}
}}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
4、如何限定型別變數
先來一段程式碼
public class ArrayAlg {
public static <T> T min(T a,T b){
if(a.compareTo(b)>0) return a;
else return b;
}}123456
首先 這段程式碼是有問題的,這是一個普通的類,寫了一個泛型方法,這個方法做的事情是比較兩個數,傳進兩個數返回一個最小值,我們怎麼確定傳進來的 a和 b 都有compareTo方法呢,這就用到了型別變數的限定
public class ArrayAlg {
public static <T extends Comparable> T main(T a,T b){
if(a.compareTo(b)>0) return a;
else return b;
}
public static <T extends Comparable> T main(T a,T b){
if(a.compareTo(b)>0) return a;
else return b;
}
public static <T,V extends Test&Comparable> T test(T a,T b){// if(a.compareTo(b)>0) return a;// else return b;
return a;
}
static class Test{}}12345678910111213141516
extends Comparable 就限定了這個泛型,傳進的值必須實現了或者繼承了Comparable這個介面,如果傳進沒有實現或繼承Comparable的類或者介面,會在編譯器就會報錯,提示你傳入的型別不對,
限定型別是可以傳多個介面的,如果是類和介面混用的話,類只能有一個,並且要放到第一個,放到第一個是java語法的規範!多個限定型別用&連線,泛型方法可以這樣限定,泛型類和泛型介面介面也可以這樣限定
5、泛型使用中的約束性和侷限性
public class Restrict<T> {
private T data;// 不能例項化型別變數
public Restrict() {
this.data = new T();//這種是不被允許的
}}123456789
public class Restrict<T> {
private T data;// 靜態域或者方法裡不能引用型別變數
private static T instance; //這個也是不被允許的}1234567
這裡為什麼靜態域或者靜態方法不能引用型別變數,是因為泛型的型別,只有在建立這個泛型型別的時候才會知道他的型別是什麼,而我們的staitc 執行時間是在構造方法前,所以他不允許,但是如果靜態方法是泛型方法,是可以引用的!
-
泛型的型別不能是基本型別 ,只能是他的包裝型別,因為基本型別不是一個物件
-
泛型不允許用instanceof 來判定型別
列印的結果是true,列印的name也是一樣的Restrict
-
Restrict 是這兩個型別的原生型別,getClass的時候也只能獲取到原生型別,獲取不到泛型型別
-
可以定義泛型陣列,但是不能建立這個陣列
- 泛型類不能繼承Exception/Throwable
但是可以throw出去
public <T extends Throwable> void doWork(T t) throws T{
try{
}catch (Throwable e){
throw t;
}
}12345678
6、泛型型別的繼承規則
public static void main(String[] args) {
Pair<Employee> employeePair=new Pair<>();
Pair<Worker> workerPair=new Pair<>();
}1234
1、Worker 是派生自 Employee,Worker是Employee的子類,但是Pair <Employee> 和 Pair <Worker>沒有任何繼承關係的
在普通的類中,是可以父類宣告直接例項化子類的
但是加上泛型類之後,就不可以了
2、泛型類可以繼承或者擴充套件其他泛型類
什麼意思呢?
public static void main(String[] args) {
Pair<Employee> employeePair=new Pair<>();
Pair<Worker> workerPair=new Pair<>();
Pair<Employee> employeePair3=new EXtendPair<>();
}
private static class EXtendPair<T> extends Pair<T>{
}123456789
上面這個圖片也說明了Pair <Employee> 和 Pair <Worker>沒有任何繼承關係,但是我也想set進去怎麼辦!萬用字元就派上用場了
7、萬用字元型別
首先我們定義幾個類Fruit Apple Orange HongFuShi,幾個類的派生關係如下
GenericType是一個很標準的泛型類,沒有任何特殊的地方
雖然Fruit和Orange是派生關係,但是print(b)會報錯的, GenericType<Fruit>和GenericType<Orange>是沒有關係的,這個上面已經說過
7.1上界萬用字元
萬用字元來了? extends Fruit,這個代表什麼意思呢?
它表示GenericType 傳進來的型別引數可以是Fruit及他的子類
public static void print2(GenericType<? extends Fruit> p){
System.out.println(p.getData().getColor());
}
public static void user2(){
GenericType<Fruit>a=new GenericType<>();
print2(a);
GenericType<Orange>b=new GenericType<>();
print2(b);
}123456789
這樣你列印b的時候就可以了,萬用字元只用於方法裡,泛型類和泛型介面是不能用的,這個和限定型別是不一樣的.
這個萬用字元規定了傳入型別的上界,只能是Fruit本身及子類,
上界萬用字元有什麼限制呢?
我往裡面set值的時候回報編譯錯誤,get的時候也只能用Fruit來接收 為什麼呢?
上界萬用字元傳入的時候 一定是Fruit的子類及本身,所以我get的時候,不管我傳進去的本身是什麼,它一定是個Fruit,但是我不能確定它是個蘋果,但是set的時候為什麼不行呢?是因為set的時候它肯定知道你是一個水果,但是你具體是那個子類,它並不知道,所以是會有問題的
所以這個上界萬用字元,只用於安全的訪問資料,
7.2下界萬用字元
由上圖可知,我們用了? super之後發現 ,我們只能放進Apple及它的父類,? super Apple 表示GenericType的引數型別的下界是Apple
使用下界萬用字元的時候,你只能set Apple 及它的子類,你不能set它的父類,get的時候只能是Objec接收,其他的都不可以,因為Objct是 所有類的父類,你放進去的時候,編譯器肯定能確定的是,它是Apple父類,但是具體是那個父類,不知道 ,但Object 一定是他們的父類,為什麼set的時候能set進去Apple的子類,是因為所有apple的子類都能安全的轉型為Apple
下界萬用字元,只能用於安全的寫入資料
8、虛擬機器是如何實現泛型的?
其實java的泛型是一個假的偽泛型,在JVM裡是用型別擦除來實現泛型的,在C#裡的泛型才是一個真真正正的泛型
public class GenericType <T>{
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}}1234567891011
雖然我們定義了一個泛型,在JDK裡, data 是一個Object就變成下邊的這樣
public class GenericType <Object>{
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}}1234567891011
但是如果是用了限定符來限定的泛型類呢,會擦除成什麼樣子的呢
public class GenericType <T extends Comparable&Serializable>{
private T data;
public T getData() {
return data;
}
public void setData( T data) {
this.data = data;
}}1234567891011
它會擦除成這樣
public class GenericType <T extends Comparable&Serializable>{
private Comparable data;
public Comparable getData() {
return data;
}
public void setData( Comparable data) {
this.data = data;
}}1234567891011
會以extens 後面的第一個型別來作為擦除型別,但是後邊的怎麼辦呢?後邊的 Serializable你在用到它的方法時,編譯器會插入一段強制轉型的程式碼
把編譯好的class開啟看一下,
擦除之後他們的型別 是一樣的,所以這種方式在編譯器中是不透過的,在位元組碼 裡有一個屬性 Signature (弱記憶)會記錄 泛型資訊 ,原始型別啥的,並不是把型別擦除的很乾淨,啥都不剩
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30239065/viewspace-2722847/,如需轉載,請註明出處,否則將追究法律責任。