Java泛型

java泛型解決容器,不確定類型問題,多個返回值,避免類型轉(zhuǎn)換。

類泛型

類泛型定義的時候需要在類型后增加尖括號,和參數(shù)類型,如下所示:

public class Holder<T> {
    private T a;

    public Holder(T a) {
        this.a = a;
    }

    public void set(T a) {
        this.a = a;
    }

    public T get() {
        return a;
    }
}
調(diào)用:
 Holder<String> h1 = new Holder<>(new String("aaa"));
 String test = h1.get();
 System.out.println(test);

這樣Holder類既可以接收任意類型的數(shù)據(jù),并保存在變量a中。

接口泛型

1. 定義接口
public interface Generator<T> {
    public T next();
}
2. 實(shí)現(xiàn)接口
public class FruitGenerator implements Generator<String> {

    private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

    @Override
    public String next() {
        Random rand = new Random();
        return fruits[rand.nextInt(3)];
    }
}
3. 調(diào)用
public class Main {

    public static void main(String[] args) {
        FruitGenerator generator = new FruitGenerator();
        System.out.println(generator.next());
        System.out.println(generator.next());
        System.out.println(generator.next());
        System.out.println(generator.next());
    }
}

數(shù)組

假設(shè)有如下的一個類:

class Generic<T> {
    
}

定義泛型數(shù)組有兩種方法:

  • 使用ArrayList
public class ListOfGenerics<T> {
    private List<T> array = new ArrayList<T>();
    public void add(T item) { array.add(item); }
    public T get(int index) { return array.get(index); }
}
  • 使用泛型,方法如下
public class GenericArrayWithTypeToken<T> {
    private T[] array;
    @SuppressWarnings("unchecked")
    public GenericArrayWithTypeToken(Class<T> type, int sz) {
        array = (T[])Array.newInstance(type, sz);
    }
    public void put(int index, T item) {
        array[index] = item;
    }
    public T get(int index) { return array[index]; }
    // Expose the underlying representation:
    public T[] rep() { return array; }
    public static void main(String[] args) {
        GenericArrayWithTypeToken<Integer> gai =
        new GenericArrayWithTypeToken<Integer>(
        Integer.class, 10);
        // This now works:
        Integer[] ia = gai.rep();
    }
}
在構(gòu)造器中傳入了 Class<T> 對象,通過 Array.newInstance(type, sz) 創(chuàng)建一個數(shù)組,這個方法會用參數(shù)中的 Class 對象作為數(shù)組元素的組件類型。這樣創(chuàng)建出的數(shù)組的元素類型便不再是 Object,而是 T。這個方法返回 Object 對象,需要把它轉(zhuǎn)型為數(shù)組。不過其他操作都不需要轉(zhuǎn)型了,包括 rep() 方法,因?yàn)閿?shù)組的實(shí)際類型與 T[] 是一致的。這是比較推薦的創(chuàng)建泛型數(shù)組的方法。

Java 中不允許直接創(chuàng)建泛型數(shù)組。

泛型和子類型

假設(shè)Apple是Fruit的子類。下面代碼是否正確:

List<Apple> apples = new ArrayList<Apple>();
List<Fruit> fruits = apples;

第一行代碼肯定是對的,但是第二行代碼不正確。這樣考慮,假定第二行代碼沒有問題,是否可以使用語句fruits.add(new Strawberry()),其中Strawberry為Fruit的子類,這樣就可以在fruits中加入草莓,但是這樣的話,fruits真正只想的對象為apples,其中只能添加Apple對象的。因此第二行代碼不正確。

通常來說,如果Foo是Bar的子類,G是一種帶泛型的類型,則G<Foo>不是G<Bar>的子類型。

類型擦除

Java中的泛型基本上都是在編譯器這個層次來實(shí)現(xiàn)的。在生成的Java字節(jié)碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數(shù),會在編譯器在編譯的時候去掉。這個過程就稱為類型擦除。對每個泛型類只生成唯一的一份目標(biāo)代碼;該泛型類的所有實(shí)例都映射到這份目標(biāo)代碼上,在需要的時候執(zhí)行類型檢查和類型轉(zhuǎn)換

在泛型代碼內(nèi)部,無法獲得任何有關(guān)泛型參數(shù)類型的信息

public class ErasedTypeEquivalence {
    public static void main(String[] args) {
        Class c1 = new ArrayList<String>().getClass();
        Class c2 = new ArrayList<Integer>().getClass();
        System.out.println(c1 == c2);
    }
}
輸出值為:true

產(chǎn)生的問題

丟失了參數(shù)的類型。不能執(zhí)行參數(shù)的方法。

重載

public class GenericTypes {  

    public static void method(List<String> list) {  
        System.out.println("invoke method(List<String> list)");  
    }  

    public static void method(List<Integer> list) {  
        System.out.println("invoke method(List<Integer> list)");  
    }  
}
上面的兩個方法是相同的。

catch異常

如果我們自定義了一個泛型異常類GenericException,那么,不要嘗試用多個catch取匹配不同的異常類型,例如你想要分別捕獲GenericException、GenericException,這也是有問題的。

當(dāng)泛型內(nèi)包含靜態(tài)變量

ublic class StaticTest{
    public static void main(String[] args){
        GT<Integer> gti = new GT<Integer>();
        gti.var=1;
        GT<String> gts = new GT<String>();
        gts.var=2;
        System.out.println(gti.var);
    }
}
class GT<T>{
    public static int var=0;
    public void nothing(T x){}
}

此時var的值為2,因?yàn)榻?jīng)過類型擦除,所有的泛型類實(shí)例都關(guān)聯(lián)到同一份字節(jié)碼上,泛型類的所有靜態(tài)變量是共享的。

instanceOf

不能對確切的泛型類型使用instanceOf操作。

泛型類型是被所有調(diào)用共享

所有泛型類的實(shí)例都共享同一個運(yùn)行時類。

擦除的補(bǔ)償

類型擦除導(dǎo)致了泛型喪失了一些功能,任何運(yùn)行期需要知道確切類型的代碼都無法工作。以下代碼無法正常工作:

public class Erased<T> {
    private final int SIZE = 100;
    public static void f(Object arg) {
    if(arg instanceof T) {} // Error
    T var = new T(); // Error
    T[] array = new T[SIZE]; // Error
    T[] array = (T)new Object[SIZE]; // Unchecked warning
    }
}

通過 new T() 創(chuàng)建對象是不行的,一是由于類型擦除,二是由于編譯器不知道 T 是否有默認(rèn)的構(gòu)造器。一種解決的辦法是傳遞一個工廠對象并且通過它創(chuàng)建新的實(shí)例。

interface FactoryI<T> {
    T create();
}
class Foo2<T> {
    private T x;
    public <F extends FactoryI<T>> Foo2(F factory) {
    x = factory.create();
    }
    // ...
}
class IntegerFactory implements FactoryI<Integer> {
    public Integer create() {
    return new Integer(0);
    }
}
class Widget {
    public static class Factory implements FactoryI<Widget> {
        public Widget create() {
            return new Widget();
        }
    }
}
public class FactoryConstraint {
    public static void main(String[] args) {
        new Foo2<Integer>(new IntegerFactory());
        new Foo2<Widget>(new Widget.Factory());
    }
}

另外一種方法是利用末班設(shè)計(jì)模式:

abstract class GenericWithCreate<T> {
    final T element;
    GenericWithCreate() { element = create(); }
    abstract T create();
}
class X {}
class Creator extends GenericWithCreate<X> {
    X create() { return new X(); }
    void f() {
    System.out.println(element.getClass().getSimpleName());
    }
}
public class CreatorGeneric {
    public static void main(String[] args) {
        Creator c = new Creator();
        c.f();
    }
}

具體類型的創(chuàng)建放到了子類繼承父類時,在 create 方法中創(chuàng)建實(shí)際的類型并返回。

邊界

協(xié)變

首先看如下的代碼:

class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}

public class CovariantArrays {
    public static void main(String[] args) {       
        Fruit[] fruit = new Apple[10];
        fruit[0] = new Apple(); // OK
        fruit[1] = new Jonathan(); // OK
        // Runtime type is Apple[], not Fruit[] or Orange[]:
        try {
            // Compiler allows you to add Fruit:
            fruit[0] = new Fruit(); // ArrayStoreException
        } catch(Exception e) { System.out.println(e); }
        try {
            // Compiler allows you to add Oranges:
            fruit[0] = new Orange(); // ArrayStoreException
        } catch(Exception e) { System.out.println(e); }
        }
} /* Output:
java.lang.ArrayStoreException: Fruit
java.lang.ArrayStoreException: Orange

main 方法中的第一行,創(chuàng)建了一個 Apple 數(shù)組并把它賦給 Fruit 數(shù)組的引用。這是有意義的,Apple 是 Fruit 的子類,一個 Apple 對象也是一種 Fruit 對象,所以一個 Apple 數(shù)組也是一種 Fruit 的數(shù)組。這稱作數(shù)組的協(xié)變。盡管 Apple[] 可以 “向上轉(zhuǎn)型” 為 Fruit[],但數(shù)組元素的實(shí)際類型還是 Apple,我們只能向數(shù)組中放入 Apple或者 Apple 的子類。在上面的代碼中,向數(shù)組中放入了 Fruit 對象和 Orange 對象。對于編譯器來說,這是可以通過編譯的,但是在運(yùn)行時期,JVM 能夠知道數(shù)組的實(shí)際類型是 Apple[],所以當(dāng)其它對象加入數(shù)組的時候就會拋出異常。

泛型設(shè)計(jì)的目的之一是要使這種運(yùn)行時期的錯誤在編譯期就能發(fā)現(xiàn),看看用泛型容器類來代替數(shù)組會發(fā)生什么:

// Compile Error: incompatible types:
ArrayList<Fruit> flist = new ArrayList<Apple>();

上面的代碼根本就無法編譯。當(dāng)涉及到泛型時, 盡管 Apple 是 Fruit 的子類型,但是 ArrayList<Apple> 不是 ArrayList<Fruit> 的子類型,泛型不支持協(xié)變。

上邊界

利用 <? extends Fruit> 形式的通配符,可以實(shí)現(xiàn)泛型的向上轉(zhuǎn)型:

public class GenericsAndCovariance {
    public static void main(String[] args) {
        // Wildcards allow covariance:
        List<? extends Fruit> flist = new ArrayList<Apple>();
        // Compile Error: can’t add any type of object:
        // flist.add(new Apple());
        // flist.add(new Fruit());
        // flist.add(new Object());
        flist.add(null); // Legal but uninteresting
        // We know that it returns at least Fruit:
        Fruit f = flist.get(0);
    }
}

通配符 List<? extends Fruit> 表示某種特定類型 ( Fruit 或者其子類 ) 的 List,但是并不關(guān)心這個實(shí)際的類型到底是什么,反正是 Fruit 的子類型,F(xiàn)ruit 是它的上邊界。那么對這樣的一個 List 我們能做什么呢?其實(shí)如果我們不知道這個 List 到底持有什么類型,怎么可能安全的添加一個對象呢?在上面的代碼中,向 flist 中添加任何對象,無論是 Apple 還是 Orange 甚至是 Fruit 對象,編譯器都不允許,唯一可以添加的是 null。所以如果做了泛型的向上轉(zhuǎn)型 (List<? extends Fruit> flist = new ArrayList<Apple>()),那么我們也就失去了向這個 List 添加任何對象的能力,即使是 Object 也不行。

我們只知道flist中的元素是Fruit的類型的子類型,具體是什么類型我們并不清楚,所有不能加入任何類型的對象。

另一方面,如果調(diào)用某個返回 Fruit 的方法,這是安全的。因?yàn)槲覀冎溃谶@個 List 中,不管它實(shí)際的類型到底是什么,但肯定能轉(zhuǎn)型為 Fruit,所以編譯器允許返回 Fruit。

下邊界

通配符的另一個方向是 “超類型的通配符“: ? super T,T 是類型參數(shù)的下界。使用這種形式的通配符,我們就可以 ”傳遞對象” 了。還是用例子解釋:

public class SuperTypeWildcards {
    static void writeTo(List<? super Apple> apples) {
        apples.add(new Apple());
        apples.add(new Jonathan());
        // apples.add(new Fruit()); // Error
    }
}

writeTo 方法的參數(shù) apples 的類型是 List<? super Apple>,它表示某種類型的 List,這個類型是 Apple 的基類型。也就是說,我們不知道實(shí)際類型是什么,但是這個類型肯定是 Apple 的父類型。因此,我們可以知道向這個 List 添加一個 Apple 或者其子類型的對象是安全的,這些對象都可以向上轉(zhuǎn)型為 Apple。但是我們不知道加入 Fruit 對象是否安全,因?yàn)槟菢訒沟眠@個 List 添加跟 Apple 無關(guān)的類型??梢詫懭胱宇惏ㄗ约旱膶ο?。

無邊界通配符

它的使用形式是一個單獨(dú)的問號:List<?>,也就是沒有任何限定。不做任何限制,跟不用類型參數(shù)的 List 有什么區(qū)別呢?

List<?> list 表示 list 是持有某種特定類型的 List,但是不知道具體是哪種類型。那么我們可以向其中添加對象嗎?當(dāng)然不可以,因?yàn)椴⒉恢缹?shí)際是哪種類型,所以不能添加任何類型,這是不安全的。而單獨(dú)的 List list ,也就是沒有傳入泛型參數(shù),表示這個 list 持有的元素的類型是 Object,因此可以添加任何類型的對象,只不過編譯器會有警告信息。

邊界通配符總結(jié)

  • 如果想從一個數(shù)據(jù)類型中獲取數(shù)據(jù),使用 ? extends 通配符
  • 如果想把對象寫入一個數(shù)據(jù)結(jié)構(gòu)里,使用 ? super 通配符
  • 如果既想存,又想取,就別使用通配符
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 定義 逆變與協(xié)變用來描述類型轉(zhuǎn)換(type transformation)后的繼承關(guān)系,其定義:如果A、B表示類型...
    開發(fā)者小王閱讀 26,048評論 4 61
  • [TOC] 深入理解 Java 泛型 概述 泛型的本質(zhì)是參數(shù)化類型,通常用于輸入?yún)?shù)、存儲類型不確定的場景。相比于...
    albon閱讀 5,798評論 0 7
  • 開發(fā)人員在使用泛型的時候,很容易根據(jù)自己的直覺而犯一些錯誤。比如一個方法如果接收List作為形式參數(shù),那么如果嘗試...
    時待吾閱讀 1,127評論 0 3
  • 芮:媽媽我再也不惹你生氣了 媽媽:真的嗎 芮:是真的 媽媽:再惹我生氣怎么辦 芮:我真的不會了 對視幾秒之后,芮小...
    讀讀寫寫蕭十一閱讀 131評論 0 0
  • 我真的要把自己玩壞了
    ca1d51a3f3f2閱讀 193評論 0 1

友情鏈接更多精彩內(nèi)容