Java高階特性泛型看這一篇就夠了

qwer1030274531發表於2020-09-21

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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章