Java語法糖3:泛型


泛型初探

在泛型(Generic type或Generics)出現之前,是這么寫代碼的:

public static void main(String[] args)
{
    List list = new ArrayList();
    list.add("123");
    list.add("456");
    
    System.out.println((String)list.get(0));
}

當然這是完全允許的,因為List里面的內容是Object類型的,自然任何對象類型都可以放入、都可以取出,但是這么寫會有兩個問題:

1、當一個對象放入集合時,集合不會記住此對象的類型,當再次從集合中取出此對象時,該對象的編譯類型變成了Object

2、運行時需要人為地強制轉換類型到具體目標,實際的程序絕不會這么簡單,一個不小心就會出現java.lang.ClassCastException,即類型轉換異常

所以,泛型出現之后,上面的代碼就改成了大家都熟知的寫法:

public static void main(String[] args)
{
    List<String> list = new ArrayList<String>();
    list.add("123");
    list.add("456");
    
    System.out.println(list.get(0));
}

這就是泛型。泛型是對Java語言類型系統的一種擴展,有點類似於C++的模板,可以把類型參數看作是使用參數化類型時指定的類型的一個占位符。引入泛型,是對Java語言一個較大的功能增強,帶來了很多的好處:

1、類型安全。類型錯誤現在在編譯期間就被捕獲到了,而不是在運行時當作java.lang.ClassCastException展示出來,將類型檢查從運行時挪到編譯時有助於開發者更容易找到錯誤,並提高程序的可靠性

2、消除了代碼中許多的強制類型轉換,增強了代碼的可讀性

3、為較大的優化帶來了可能

 

getClass()相同

看一段代碼:

public static void main(String[] args)
{
    List<String> stringList = new ArrayList<String>();
    List<Integer> integerList = new ArrayList<Integer>();
    System.out.println(stringList.getClass() == integerList.getClass());
}

運行結果為:

true

這意味着,泛型是什么並不會對一個對象實例是什么類型的造成影響,所以,通過改變泛型的方式試圖定義不同的重載方法也是不可以的:

 

 

盡量使用精確的類型定義泛型

盡量使用精確的類型定義泛型,除非必要,否則不要寫一個接口或者父類上去:

public static void main(String[] args)
{
    List<Number> list = new ArrayList<Number>();
    list.add(4);
    list.add(2.2);
    for (Number number : list)
        System.out.println(number);
}

就像這樣,list中的是一個Number類型,往里面添加的是Integer與Double,這樣導致get出來的元素也都是Number類型的,失去了子類擴展的功能。如果要讓子類變為Interger和Double也可以,(Integer)list.get(0)和(Double)list.get(1)強轉就可以了,但是這樣不就失去了泛型的意義了嗎?所以,盡量用精確的類型去定義泛型。

 

使用類型通配符

List<Object>不是List<String>的父類型,List<Integer>不是List<Number>的父類型,試圖用以下方式賦值是不允許的:

1 public static void main(String[] args)
2 {
3     List<Number> numberList = new ArrayList<Number>();
4     List<Integer> integerList = new ArrayList<Integer>();
5     numberList = integerList;
6 }

第5行將報錯"Type mismatch: cannot convert from List<Integer> to List<Number>"。有人可能覺得這樣很不方便:我在一個方法里面只需要循環檢索一個List,也不能利用多態放一個父類型進去,也不能重載,那怎么辦呢?針對這個問題,Java給開發者提供了通配符"?",看一下:

public static void main(String[] args)
{
    List<String>  stringList = new ArrayList<String>();
    List<Integer> integerList = new ArrayList<Integer>();
        
    printList(stringList);
    printList(integerList);
}
    
private static void printList(List<?> l)
{
    for (Object o : l)
        System.out.println(o);
}

<?>是類型通配符,表示是任何泛型的父類型,這樣List<Object>、List<String>這些都可以傳遞進入printList方法中,注意這里的參數不能寫成List<E>,這樣就報錯了,E未定義。當然<?>也可以不加,不過這樣會有警告:如果傳遞一個List<E>給List,相當於傳遞一個只承諾將它當作List(原始類型)的方法,這將會破壞使用泛型的類型安全

再注意一點,使用類型通配符,只能從中檢索元素,不能添加元素

 

泛型方法

public static void main(String[] args)
{
    System.out.println(ifThenElse(false, "111", "222"));
}
    
private static <T> T ifThenElse(boolean b, T first, T second)
{
    return b ? first : second;
}

返回結果為:

222

這說明,方法也可以被泛型化,不管定義在其中的類是不是泛型化的。這意味着不用顯式告訴編譯器,想要T什么值:編譯器只知道這些T都必須相同。

 

靜態資源不認識泛型

接上一個話題,如果把<T>去掉,那么:

報錯,T未定義。但是如果我們再把static去掉:

這並不會有任何問題。兩相對比下,可以看出static方法並不認識泛型,所以我們要加上一個<T>,告訴static方法,后面的T是一個泛型。既然static方法不認識泛型,那我們看一下static變量是否認識泛型:

 

這證明了,static變量也不認識泛型,其實不僅僅是static方法、static變量,static塊也不認識泛型,可以自己試一下。總結起來就是一句話:靜態資源不認識泛型

 

泛型約束

可以對泛型參數作約束,本來覺得應該有規律,后來發現沒有,那就把自己研究的結論發一下,假設有一組類繼承關系C繼承自B,B繼承自A:

1、定義class的時候只能使用extends關鍵字且不能用通配符"?"

public class TestMain<T extends B>
{
    public static void main(String[] args)
    {
        new TestMain<C>();
    }
}

正確。TestMain類的泛型只能傳B的子類,也就是C。"new TestMain<A>()"、"public class TestMain<? extends B>"、"public class TestMain<T super B>"都是錯誤的寫法

2、作為方法的參數,泛型可以使用"? extends B"或者"? super B",前者表示實際類型只可以是B的子類,后者表示實際類型只可以是B的父類,以下兩種寫法都是正確的:

public static void main(String[] args)
{
    print(new ArrayList<C>());
}
    
public static void print(List<? extends B> list)
{
    
}
public static void main(String[] args)
{
    print(new ArrayList<A>());
    print(new ArrayList<Object>());
}

public static void print(List<? super B> list)
{
        
}

3、作為局部變量的參數,泛型可以使用"? extends B"或者"? super B",不過前者好像沒什么意義,后者表示只可以傳以B為父類的對象,所以以下的寫法是正確的:

public static void main(String[] args)
{
    List<? super B> list = new ArrayList<B>();
    list.add(new C());
}

不要寫"list.add(new A())",JDK將會認為這是類型不匹配的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM