1. 程式人生 > >Java泛型(一):入門、原理、使用

Java泛型(一):入門、原理、使用

core clas set out keyword getclass code 避免 post

遠在 JDK 1.4 版本的時候,那時候是沒有泛型的概念的。當時 Java 程序員們寫集合類的代碼都是類似於下面這樣:

List list = new ArrayList();
list.add("www.cnblogs.com");
list.add(23);
String name = (String)list.get(0);
Integer number = (Integer)list.get(1);

在代碼中聲明一個集合,我們可以往集合中放入各種各樣的數據,而在取出來的時候就進行強制類型轉換。

但其實這樣的代碼存在一定隱患,因為可能過了不久我們就會忘記到底我們存放的 list 裏面到底第幾個是 String,第幾個是 Integer 了。這樣就會出現下面這樣的情況:

List list = new ArrayList();
list.add("www.cnblogs.com");
list.add(23);
String name = (String)list.get(0);
String number = (String)list.get(1);    //ClassCastException

上面的代碼在運行時會發生強制類型轉換異常。這是因為我們在存入的時候,第二個是一個 Integer 類型,但是取出來的時候卻將其強制轉換為 String 類型了。

泛型的誕生

Sun 公司為了使 Java 語言更加安全,減少運行時異常的發生。於是在 JDK 1.5 之後推出了泛型的概念。

於是在 JDK 1.5 之後,我們如果使用集合來書寫代碼,可以使用下面這種形式:

List<String> list = new ArrayList();
list.add("www.cnblogs.com");
list.add("www.cnblogs.com/chanshuyi");
String cnBlogs = list.get(0);
String myWebSite = list.get(1); 

泛型就是將類型參數化,其在編譯時才確定具體的參數。在上面這個例子中,這個具體的類型就是 String。可以看到我們在創建 List 集合的時候指定了 String 類型,這就意味著我們只能往 List 集合中存放 String 類型的數據。而當我們指定泛型之後,我們去取出數據後就不再需要進行強制類型轉換了,這樣就減少了發生強制類型轉換的風險。

上面我們通過兩個很簡單的例子知道了為什麽要有泛型,以及泛型最簡單的使用。下面我們通過一個面試中常見的例子來看一下泛型的本質是什麽。

泛型的本質

ArrayList<String> a = new ArrayList<String>();
ArrayList<Integer> b = new ArrayList<Integer>();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2); 

在繼續往下看之前,先想一想,這道題輸出的結果是什麽?

是 true 還是 false ?

這道題輸出的結果是 true。因為無論對於 ArrayList 還是 ArrayList,它們的 Class 類型都是一直的,都是 ArrayList.class。

那它們聲明時指定的 String 和 Integer 到底體現在哪裏呢?

答案是體現在類編譯的時候。

當 JVM 進行類編譯時,會進行泛型檢查,如果一個集合被聲明為 String 類型,那麽它往該集合存取數據的時候就會對數據進行判斷,從而避免存入或取出錯誤的數據。

也就是說:泛型只存在於編譯階段,而不存在於運行階段。在編譯後的 class 文件中,是沒有泛型這個概念的。

上面我們只是說了泛型在集合中的使用方式,但其實泛型的應用範圍不僅僅只是集合,還包括類、方法、Map 接口等等。

泛型的應用還廣泛存在於下面幾種情形:泛型類、泛型方法、泛型集合。

泛型類

泛型類一般使用字母 T 作為泛型的標誌。

public class GenericClass<T> {
    private T object;
    public T getObject() {
        return object;
    }
    public void setObject(T object) {
        this.object = object;
    }
}

使用:

public static void main(String[] args) {
    GenericClass<Integer> integerGenericClass = new GenericClass<>(100);
    integerGenericClass.showType();
    GenericClass<String> stringGenericClass = new GenericClass<>("www.cnblogs.com/chanshuyi");
    stringGenericClass.showType();
}

泛型方法

泛型方法一般使用字母 T 作為泛型的標誌。

public class GenericMethod {
    public static <T> T getObject(Class<T> clz) throws InstantiationException, IllegalAccessException{
        T t = clz.newInstance();
        return t;
    }
}

使用:

public static void main(String[] args) throws Exception{
    GenericMethod genericMethod = getObject(GenericMethod.class);
    System.out.println("Class:" + genericMethod.getClass().getName());
}

泛型集合

除了使用 T 作為泛型類的標誌之外,在需要使用 Map 的類中,通常使用 K V 兩個字母表示 Key Value 對應的類型。

public class GenericMap<K, V> {
    private K key;
    private V value;
    public void put(K key, V value) {
        this.key = key;
        this.value = value;
    }
}

使用:

public static void main(String[] args) {
        GenericMap<Integer, String> team = new GenericMap<>();
        team.put(1, "YaoMin");
        team.put(2, "Me");
        GenericMap<String, Integer> score = new GenericMap<>();
        score.put("YaoMin", 88);
        score.put("Me", 80);
    }

總結

我們通過一個簡單的例子理解了泛型誕生的緣由,之後又通過一個常見的面試題明白了泛型只存在於編譯期間的本質,最後介紹了常見的泛型使用場景。

下篇文章,我們將介紹泛型中通配符的使用。

Java泛型(一):入門、原理、使用