說說,Java泛型

###特性
泛型只在編譯階段有效

public class GenericTest1 {

    public static void main(String[] args) {

        ArrayList<String> strings = new ArrayList<>();
        ArrayList<Double> doubles = new ArrayList<>();

        System.out.println(strings.getClass().equals(doubles.getClass()));
    }
}

輸出:true

編譯之後程序會採取去泛型化的措施。也就是說Java中的泛型,只在編譯階段有效。在編譯過程中,正確檢驗泛型結果後,會將泛型的相關信息擦出,並且在對象進入和離開方法的邊界處添加類型檢查和類型轉換的方法。也就是說,泛型信息不會進入到運行時階段。

對此總結成一句話:泛型類型在邏輯上看以看成是多個不同的類型,實際上都是相同的基本類型

###泛型的使用
泛型的使用,分爲:泛型類,泛型方法,泛型接口
####泛型類
泛型類型用於類的定義中,被稱爲泛型類
語法:

class 類名稱 <泛型標識:可以隨便寫任意標識號,標識指定的泛型的類型>{
  private 泛型標識 /*(成員變量類型)*/ var; 
  .....

  }
}
public class GenericTest2<T extends Number> {

    T value;

    public T getValue() {
        return value;
    }
}

###泛型接口
泛型接口與泛型類的定義及使用基本相同。

  • 當實現泛型接口的類,未傳入泛型實參時:與泛型類的定義相同,在聲明類的時候,需將泛型的聲明也一起加到類中
  • 當實現泛型接口的類,傳入泛型實參時:則所有使用泛型的地方都要替換成傳入的實參類型
interface GenericInterfaceTest3<T> {

    public T next();
}

/**
 * 未傳入泛型實參時
 * @param <T>
 */
class GenericInterfaceClassTest3<T> implements GenericInterfaceTest3<T> {

    @Override
    public T next() {
        return null;
    }
}

/**
 * 傳入泛型實參時:
 */
public class GenericTest3 implements GenericInterfaceTest3<String> {

    String[] array = new String[]{"A", "B", "C"};
    int count = 0;

    @Override
    public String next() {
        count ++;
        return array[count % 3];
    }

    public static void main(String[] args) {
        GenericTest3 genericTest3 = new GenericTest3();
        for (int i =0 ; i< 5; i++) {
            System.out.println(genericTest3.next());

        }
    }
}

####泛型方法
泛型類,是在實例化類的時候指明泛型的具體類型;泛型方法,是在調用方法的時候指明泛型的具體類型 。

/**
 * 泛型方法的基本介紹
 * @param tClass 傳入的泛型實參
 * @return T 返回值爲T類型
 * 說明:
 *     1)public 與 返回值中間<T>非常重要,可以理解爲聲明此方法爲泛型方法。
 *     2)只有聲明瞭<T>的方法纔是泛型方法,泛型類中的使用了泛型的成員方法並不是泛型方法。
 *     3)<T>表明該方法將使用泛型類型T,此時纔可以在方法中使用泛型類型T。
 *     4)與泛型類的定義一樣,此處T可以隨便寫爲任意標識,常見的如T、E、K、V等形式的參數常用於表示泛型。
 */
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  IllegalAccessException{
     
}

example

public class GenericMethod<T> {

    T value;

    public GenericMethod(T value) {
        this.value = value;
    }

    /**
     * 雖然在方法中使用了泛型,但是這並不是一個泛型方法。
     *  這只是類中一個普通的成員方法,只不過他的返回值是在聲明泛型類已經聲明過的泛型。
     *  所以在這個方法中才可以繼續使用 T 這個泛型。
     * @return
     */
    public T getValue() {
        return value;
    }

    public void showValue(T genericObj){
        System.out.println(value + " / " + genericObj);
    }

    public <T>  T getLastValue(List<T> list) {
        return (list.get(list.size() -1));
    }

    public <T>  T getRandomValue(List<GenericMethod<T>> list) {
        final Random random = new Random();


        final GenericMethod<T> tGenericMethod = list.get(random.nextInt(list.size()));
        return tGenericMethod.value;
    }

    public static void main(String[] args) {

        final GenericMethod<String> stringGenericMethod = new GenericMethod<String>("23");
        stringGenericMethod.showValue("5");

        final ArrayList<GenericMethod<String>> objects = new ArrayList<>();
        objects.add(stringGenericMethod);
        objects.add(new GenericMethod<>("2ggg3"));
        System.out.println(stringGenericMethod.getLastValue(objects).getValue());
        System.out.println(stringGenericMethod.getRandomValue(objects));

    }
}

#####可變參數的泛型方法

public class GenericMethod2<T> {

    public <T> void  out(T ... args) {

        for (T t : args) {
            System.out.println(t);
        }
    }

    public static void main(String[] args) {
        new GenericMethod2<>().out("A", 1, 1999L,0.3);
    }
}
靜態方法與泛型

靜態方法無法訪問類上定義的泛型;如果靜態方法操作的引用數據類型不確定的時候,必須要將泛型定義在方法上。即:如果靜態方法要使用泛型的話,必須將靜態方法也定義成泛型方法 。

public class StaticGenericMethod<T> {
    
// error    
//    public static void out(T t) {
//        
//    }

    public static <T>  void out(T t) {

        System.out.println(t);
    }


    public static void main(String[] args) {
        
        StaticGenericMethod.out("a");
    }
}

###?super T 和? extends T區別

<? extends T>和<? super T>是Java泛型中的“通配符(Wildcards)”和“邊界(Bounds)”的概念。 - <? extends T>:是指 “上界通配符(Upper Bounds Wildcards)” - <? super T>:是指 “下界通配符(Lower Bounds Wildcards)” ``` class Food { } class Fruit extends Food { } class Apple extends Fruit { } class RedApple extends Apple { } public class GenericExtendsSuperTest { public void genericExtends() { final List<? extends Fruit> fruits = new ArrayList<>(); //error //fruits.add(new Apple()); //fruits.add(new Fruit()); //fruits.add(new Object()); fruits.add(null); final Fruit fruit = fruits.get(0); final Apple apple = (Apple)fruits.get(0); fruits.contains(new Fruit()); } public void genericSuper() { final List<? super Fruit> fruits = new ArrayList<>(); fruits.add(new Apple()); fruits.add(new Fruit()); fruits.add(null); //error //final Fruit fruit = fruits.get(0); //final Apple apple = (Apple)fruits.get(0); //fruits.contains(new Fruit()); } } ``` ####知乎 > 作者:袁太富 鏈接:https://www.zhihu.com/question/20400700/answer/102419313 > 來源:知乎 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。 > > List :”存的時候只能選一個類型。“List <? extends Fruit> 意思:List中所有元素都是Fruit的子類(包含本身), > List <? super Fruit> 意思:List中所有元素都是Fruit的父類(包含本身) > 1、List <? extends Fruit>假設:Fruit有子類A、B、C 那麼 list.add(A);list.add(B);list.add(C);顯然錯誤(不能存多個類)。雖然我們現在看的是ABC3個類就會問爲什麼會把不同類型的存進去,我這樣存不就好了。list.add(A);list.add(A);其實這也是錯誤的,因爲在運行之前他可不知道你到底add進去的東西是什麼類型,是一樣還是不一樣,因實例化的時候是? 待定。爲了避免類型不同的情況,所以會編譯不通過。 > 2、List <? super Fruit> 假設:Fruit有子類A、B、C 那麼 list.add(A);list.add(B);list.add(C); 這卻是可以的,爲什麼呢:因爲他是這麼存的:list.add((Fruit)A);list.add((Fruit)B); 自動強轉了。因爲小轉大是隱性的,大轉小纔是強轉需要加類型。那這裏爲什麼又不能存Fruit的父類呢? 因爲見假設1,它是?號,類型代表待定,不跑起來他也不知道你到底存的什麼。所以我們能手動add()進去的數據都必須是絕對安全的(最低級父類:本身)才能通過。所以直接add父類也是不行的。 ####PECS原則 我們可以總結出一條規律,”Producer Extends, Consumer Super”: - “Producer Extends” – 如果你需要一個只讀List,用它來produce T,那麼使用? extends T。 - “Consumer Super” – 如果你需要一個只寫List,用它來consume T,那麼使用? super T。 - 如果需要同時讀取以及寫入,那麼我們就不能使用通配符了。 **或者** extends 可用於的返回類型限定,不能用於參數類型限定。 super 可用於參數類型限定,不能用於返回類型限定。 >帶有super超類型限定的通配符可以向泛型對易用寫入,帶有extends子類型限定的通配符可以向泛型對象讀取。——《Core Java》 ###類型擦除 類型擦除就是說Java泛型只能用於在編譯期間的靜態類型檢查,然後編譯器生成的代碼會擦除相應的類型信息,這樣到了運行期間實際上JVM根本就知道泛型所代表的具體類型。這樣做的目的是因爲Java泛型是1.5之後才被引入的,爲了保持向下的兼容性,所以只能做類型擦除來兼容以前的非泛型代碼。 我們先來看一下下面這個簡單的例子: ``` public class Node { private T data; private Node next; public Node(T data, Node next) { this.data = data; this.next = next; } public T getData() { return data; } // ... } ``` 編譯器做完相應的類型檢查之後,實際上到了運行期間上面這段代碼實際上將轉換成: ``` public class Node { private Object data; private Node next; public Node(Object data, Node next) { this.data = data; this.next = next; } public Object getData() { return data; } // ... } ``` 這意味着不管我們聲明Node還是Node,到了運行期間,JVM統統視爲Node。有沒有什麼辦法可以解決這個問題呢?這就需要我們自己重新設置bounds了,將上面的代碼修改成下面這樣: ``` public class Node
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章