Java 泛型

ejnx8053 7年前發布 | 13K 次閱讀 泛型 Java Java開發

泛型入門

編譯時不檢查類型的異常

public class ListErr
{
    public static void main(String[] args)
    {
        // 創建一個只想保存字符串的List集合
        List strList = new ArrayList();
        strList.add("布達佩斯");
        strList.add("布拉格");
        // "不小心"把一個Integer對象"丟進"了集合
        strList.add(34);     // ①
        strList.forEach(str -> System.out.println(((String)str).length())); // ②
    }
}

上述程序創建了一個List集合,且該List集合保存字符串對象——但程序不能進行任何限制,如果程序在①處“不小心”把一個Integer對象"丟進"了List集合,這將導致程序在②處引發ClassCastException異常,因為程序試圖把一個Integer對象轉換為String類型

使用泛型

參數化類型,允許程序在創建集合時指定集合元素的類型。Java的參數化類型被稱為泛型(Generic)

class GenericList
{
    public static void main(String[] args)
    {
        // 創建一個只想保存字符串的List集合
        List<String> strList = new ArrayList<>();     // ①
        strList.add("布達佩斯");
        strList.add("布拉格");
        // 下面代碼將引起編譯錯誤
        strList.add(34);     // ②
        strList.forEach(str -> System.out.println(((String)str).length())); // ③
    }
}

strList集合只能保存字符串對象,不能保存其他類型的對象。創建特殊集合的方法是:在集合接口、類后增加尖括號,尖括號里放一個數據類型,即表明這個集合接口、集合類只能保存特定類型的對象。

①類型聲明,在創建這個ArrayList對象時也指定了一個類型參數;②引發編譯異常;③不需要進行強制類型轉換

泛型使程序更加簡潔,集合自動記住所有集合元素的數據類型,從而無須對集合元素進行強制類型轉換

Java7泛型的“菱形”語法

Java允許在構造器后不需要帶完整的泛型信息,只要給出一對尖括號(<>)即可,Java可以推斷尖括號里應該是什么泛型信息

public class DiamondTest
{
    public static void main(String[] args)
    {
        // Java自動推斷出ArrayList的<>里應該是String
        List<String> countries = new ArrayList<>();
        countries.add("法蘭西第五共和國");
        countries.add("西班牙王國");
        // 遍歷countries集合,集合元素就是String類型
        countries.forEach(ele -> System.out.println(ele.length()));
        // Java自動推斷出HashMap的<>里應該是String , List<String>
        Map<String , List<String>> citiesInfo = new HashMap<>();
        // Java自動推斷出ArrayList的<>里應該是String
        List<String> cities = new ArrayList<>();
        cities.add("巴黎");
        cities.add("巴塞羅那");
        citiesInfo.put("Bienvenue" , cities);
        // 遍歷Map時,Map的key是String類型,value是List<String>類型
        citiesInfo.forEach((key , value) -> System.out.println(key + "-->" + value));
    }
}

深入泛型

所謂泛型:就是允許在定義類、接口、方法時指定類型形參,這個類型形參將在聲明變量、創建對象、調用方法時動態地指定(即傳入實際的類型參數,也可稱為類型實參)

定義泛型接口、類

List接口、Iterator接口、Map的代碼片段

// 定義接口時指定了一個類型形參,該形參名為E
public interface List<E>
{ // 在該接口里,E可作為類型使用 // 下面方法可以使用E作為參數類型 void add(E x); Iterator<E> iterator(); // ①

}

// 定義接口時指定了一個類型形參,該形參為E public interface Iterator<E> { // 在該接口里E完全可以作為類型使用 E next(); boolean hasNext(); }

// 定義該接口時指定了兩個類型形參,其形參名為K、V public interface Map<K, V> { // 在該接口里K、V完全可以作為類型使用 Set<K> keySet(); // ② V put(K key,V value); }</code></pre>

上面代碼說明泛型實質:允許在定義接口、類時聲明類型形參,類型形參在整個接口、類體內可當成類型使用

包含泛型聲明的類型可以在定義變量、創建對象時傳入一個類型實參,從而可以動態地生成無數個邏輯上的子類,但這種子類在物理上并不存在

可以為任何類、接口增加泛型聲明(并不是只有集合類才可以使用泛型聲明,雖然集合類是泛型的重要使用場所)

// 定義Onmyoji類時使用了泛型聲明
public class Onmyoji<T>
{
    // 使用T類型形參定義實例變量
    private T info;
    public Onmyoji(){}
    // 下面方法使用T類型形參來定義構造器
    public Onmyoji(T info)
    {
        this.info = info;
    }
    public T getInfo() 
    {
        return info;
    }
    public void setInfo(T info) 
    {
        this.info = info;
    }
    public static void main(String[] args) 
    {
        //由于傳給T形參的是String,所以構造器參數只能是String
        Onmyoji<String> a1 = new Onmyoji<>("安倍晴明");
        System.out.println(a1.getInfo());
        // 由于傳給T形參的是Double,所以構造器參數只能是Double或double
        Onmyoji<Double> a2 = new Onmyoji<>(520.1314);
        System.out.println(a2.getInfo());
    }
}

當創建帶泛型聲明的自定義類,為該類定義構造器時,構造器名還是原來的類名,不要增加泛型聲明

從泛型類派生子類

當創建了帶泛型聲明的接口、父類之后,可以為該接口創建實現類,或從父類派生子類。當使用這些接口、父類時不能再包含類型形參

// 定義類Shikigami繼承Onmyoji類,Onmyoji類不能跟類型形參
public class Shikigami extends Onmyoji<T>{ }        // 錯誤

方法中的形參(或數據形參)代表變量、常量、表達式等數據。定義方法時,可以聲明數據形參;調用方法(使用方法)時,必須為這些數據形參傳入實際的數據;與此類似的是,定義類、接口、方法時可以聲明類型形參,使用類、接口、方法時應為類型形參傳入實際的類型

// 使用Onmyoji類時為T形參傳入String類型
public class Shikigami extends Onmyoji<String>{ }        // 正確

調用方法時必須為所有的數據參數傳入參數值,而使用類、接口時也可以不為類型形參傳入實際的類型參數

// 使用Onmyoji類時,沒有為T形參傳入實際的類型參數
public class Shikigami extends Onmyoji{ }        // 正確

子類需要重寫父類的Getters和Setters方法

private String info;
public String getInfo() 
{
    return "子類"+ super.getInfo();
}
public void setInfo(String info)
{
    this.info = info;
}

并不存在泛型類

ArrayList<String>類,是一種特殊的ArrayList類。該ArrayList<String>對象只能添加String對象作為集合元素。但實際上,系統并沒有為ArrayList<String>生成新的class文件,而且也不會把ArrayList<String>當成新類來處理。因為不管泛型的時間類型參數是什么,它們在運行時總有同樣的類(class)

// 分別創建List<String>對象和List<Integer>對象
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
// 調用getClass()方法來比較l1和l2的類是否相等
System.out.println(l1.getClass() == l2.getClass());        // 輸出true

不管為泛型的類型形參傳入哪一種類型實參,對于Java來說,它們依然被當成同一個類處理,在內存中也只占用一塊內存空間,因此在靜態方法、靜態初始化塊或者靜態變量的聲明和初始化中不允許使用類型形參

由于系統中并不會真正生成泛型類,所以instanceof運算符后不能使用泛型類。

if(cs instanceof List<String>)
{
    ...
}

類型通配符

如果Foo是Bar的一個子類型(子類或者子接口),而G是具有泛型聲明的類或接口,G<Foo>并不是G<Bar>的子類型

數組和泛型有所不同,假設Foo是Bar的一個子類型(子類或者子接口),那么Foo[]依然是Bar[]的子類型;但G<Foo>不是G<Bar>的子類型

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

使用類型通配符

為了表示各種泛型List的父類,可以使用類型通配符,類型通配符是一個問號(?),將一個問號作為類型實參傳給List集合,寫作:List<?>(意思是未知類型元素的List)。這個問號(?)被稱為通配符,它的元素類型可以匹配任何類型

public void test(List<?> c)
{
    for(int i = 0; i < c.size(); i++)
    {
        System.out.println(c.get(i));
    }
}

現在使用任何類型的List來調用它,程序依然可以訪問集合c中的元素,其類型是Object,這永遠是安全的,因為不管List的真實類型是什么,它包含的都是 Object

這種帶通配符的List僅表示它是各種泛型List的父類,并不能把元素加入到其中

List<?> c = new ArrayList<String>();    
// 下面程序引起編譯錯誤
c.add(new Object());

因為程序無法確認c集合里元素的類型,所以不能向其中添加對象。根據前面的List<E>接口定義的代碼可以發現:add ()方法由類型參數E作為集合的元素類型,所以傳給add的參數必須是E類的對象或者其子類的對象。但因為在該例中不知道E是什么類型,所以程序無法將任何對象“丟進”該集合。唯一的例外是 null,它是所有引用類型的實例

另一方面,程序可以調用get()方法來返回List<?>集合指定索引處的元素,其返回值是一個未知類型,但可以肯定的是,它總是一個Object。因此,把get()的返回值賦值給一個Object類型的變量,或者放在任何希望是Object類型的地方都可以

設定類型通配符的上限

List<Circle>并不是List<Shape>的子類型,所以不能把List<Circle>對象當成List<Shape>使用。為了表示List<Circle>的父類,使用List<? extends Shape>

List<? extends Shape>是受限通配符的例子,此處的問號(?)代表一個未知的類型,此處的未知類型一定是Shape的子類也可以是Shape,因此可以把shape稱為這個通配符的上限(upper bound)

設定類型形參的上限

Java泛型不僅允許在使用通配符形參時設定上限,而且可以在定義類型形參時設定上限,用于表示傳給該類型形參的實際類型要么是該上限類型,要門是該上限類型的子類

在一種更極端的情況下,程序需要為類型形參設定多個上限(至少有一個父類上限,可以有多個接口上限),表明該類型形參必須是其父類的子類(其父類本事也行),并且實現多個上限接口

//表明T類型必須是Number類或其子類,并必須實現java.io.Serializablepublic 
class Apple<T extends Number & java.io.Serilizable>
{
    ...
}

與類同時繼承父類、實現接口類似的是:為類型形參指定多個上限,所有的接口上限必須位于類上限之后。也就是說,如果需要為類型形參指定類上限,類上限必須位于第一位

泛型方法

定義泛型方法

Java不允許把對象放進一個未知類型的集合中

所謂泛型方法,就是在聲明方法時定義一個或多個類型形參

修飾符 <T, S> 返回值類型 方法名(形參列表)
{
    // 方法體...
}

 

來自:https://segmentfault.com/a/1190000007983793

 

 本文由用戶 ejnx8053 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!