泛型

所謂泛型,就是容許在定義類、接口、方法時使用類型形參,這個類型形參將在聲明變量、建立對象、調用方法時動態地指定。java

集合中的泛型

泛型最多見在使用集合時,如:數組

List<String> list=new ArrayList<String>();

能夠簡寫爲:ui

List<String> list=new ArrayList<>();

泛型集合與泛型數組的不一樣

若是B是A的子類,對於數組而言,B[]A[]的子類型,可是對於泛型,G<B>不是G<A>的子類型,下面第二行就會編譯出錯。this

Object[] a=new String[10];
List<Object> a1=new ArrayList<String>();//編譯錯誤

下面咱們來探討一下緣由。
按照上面的邏輯,咱們能夠作下面實驗:設計

Integer[] ia=new Integer[5];
Number[] na=ia;
na[0]=0.5;//編譯正常,運行錯誤,有風險

上面的代碼編譯正常,但運行到na[0]=0.5;會拋出一個ArrayStoreException異常,這是一種潛在的風險。
在Java的早期設計中,容許Integer[]數組賦值給Number[]變量存在缺陷,所以Java在泛型設計是進行了改進,它再也不容許把List 對象賦值給List 變量。例如,下面的代碼會編譯錯誤: code

List<Integer> iList=new ArrayList<>();
List<Number> nList=iList;

Java泛型設計的原則是,只要代碼在編譯時沒有出現警告,就不會遇到運行時異常。對象

定義泛型接口、類

能夠在任意類、接口的定義是增長泛型聲明:繼承

public class Apple <T>{
    private T info;
    public Apple(T info){
        this.info=info;
    }
}

當從泛型類中派生子類時,必需要指明參數類型。接口

public class Apple extends Fruit<String>

能夠用以下方式聲明一個泛型引用:get

Fruit<String> fruit;

泛型方法

能夠用以下方式定義泛型方法:

static <T> void fromArrayToCollection(T[] a, Collection<T> c){
    for (T o:a){
        c.add(o);
    }
}

泛型構造器

和定義泛型方法同樣:

class Foo{
    <T> Foo(T t){
        System.out.println(t);
    }
}

使用以下:

Foo foo=new <String> Foo("hello");

通配符

類型通配符?

List<?>,這種帶通配符的List表示它是各類泛型的父類,並不能把元素加入其中。例如,下面代碼將會引發編譯錯誤。

List<?> list=new ArrayList<>();
list.add(new Object());//編譯錯誤
Object o=list1.get(0);

使用通配符的集合只能向其中取元素,不能往裏面添加元素

設定類型通配符的上限:? extends T

用法以下:

List<? extends Number> list3=new ArrayList<>();
list3.add(new Integer());//編譯錯誤
Number str=list3.get(0);

這種方式聲明的集合只能取元素,不能往裏放元素,應爲聲明該集合時,只知道元素類型繼承自Number,但不知道該集合中元素的具體類型,因此不能往裏放元素;由於知道里面的元素必定是Number的子類,因此能夠取出來

設定類型通配符的下限:? super T

用法以下:

List<? super Number> list4=new ArrayList<>();
list4.add(new Integer(1));
Object o1=list4.get(0);

這種方式聲明集合時,能夠往裏面放元素,也能夠從裏面取元素。由於對於List<? super Number>,能夠知道集合元素聲明的類型必定是Number的父類,因此往裏面放Number及其子類的元素,並且取出來的元素必定是Object類型。

擦除與轉換

在嚴格的泛型代碼裏,帶泛型聲明的類應該帶着類型參數。但爲了與老的Java代碼保持一致,也容許在使用帶泛型聲明的類時不指定實際的類型參數。若是沒有爲這個泛型類指定實際的類型參數,則該類型參數被稱做raw type(原始類型)。
當把一個具備泛型信息的對象賦值給另外一個沒有泛型信息的變量時,全部尖括號之間的類型信息都將被扔掉。好比一個List 類型被轉換爲List,則該List對集合元素的類型檢查變成了類型參數的上限(Object)。

class Orange<T extends Number>{
    T size;
    public Orange(){
        
    }
    public Orange(T size){
        this.size=size;
    }
    public void setSize(T size){
        this.size=size;
    }
    public T getSize(){
        return this.size;
    }
}
public class ErasureTest {
    public static void main(String[] args){
        Orange<Integer> a=new Orange<>(5);
        Integer as=a.getSize();
        //將a對象賦給b變量,將丟失尖括號裏的類型信息
        Orange b=a;
        //b只知道size的類型爲Number
        Number size1=b.getSize();
        //下面代碼引發編譯錯誤
        Integer size2=b.getSize();
    }
}

對泛型而言,能夠直接將一個List對象賦值給一個List<String>對象,以下程序:

List<Integer> li=new ArrayList<>();
li.add(5);
li.add(9);
List list=li;
//下面代碼引編譯經過
List<String> ls=list;
//但從集合裏取元素時會引發運行時異常
System.out.println(ls.get(0));

上面代碼編譯正常,但運行時會拋出ClassCastException異常,由於li變量聲明的是List<String>,實際引用的是List<Integer>集合,因此當試圖把該集合裏的元素當成String類型的對象取出時,將引起運行時異常。

相關文章
相關標籤/搜索