一個小栗子聊聊 JAVA 泛型基礎
背景
周五本該是愉快的,可是花了一個早上查問題,為什么要花一個早上?我把原因總結為兩點:
- 日志信息嚴重丟失,茫茫代碼毫無頭緒。
- 對泛型的認識不夠,導致代碼出現了BUG。
第一個原因可以通過以后編碼謹慎的打日志來解決,我們今天主要來一起回顧下 JAVA 泛型基礎。
一個小栗子
先看下面一個例子,test1實例化一個List容器的時候沒有指定泛型參數,那么我們可以往這個容器里面放入任何類型的對象,這樣是不是很爽?但是 當我們從容器中取出容器中的對象的時候我們必須小心翼翼,因為容器中的對象具有運行時的類型信息,這意味著你不能夠將一個帶有運行時類型信息的對象賦值給另一個類型,否則ClassCastException 。
@Test
public void test1() throws Exception {
Listlist = new ArrayList();
list.add("float.lu");
list.add(1);
String name = (String) list.get(0);
int num = (Integer) list.get(1);
System.out.println(String.format("name[%s], num[%s]", name, num));
}
</code></pre>
上面的代碼沒問題,可以很好地編譯和運行通過,問題是我必須要事先很清楚地知道容器中的索引為0的對象是什么類型,索引為1的對象是什么類型,很顯然,這在實際應用中是不切實際的,也是一種很不靠譜的做法,那么這個問題如何解決呢?泛型。
引入泛型
為了解決這個問題,我們引入泛型,下面代碼可以看出與上面不同的是我們在實例化容器的時候加了這個東西,這個東西的學名叫做 泛型參數 ,就像普通方法帶有參數一樣,interface List中的E為形式參數、而String為實參。
@Test
public void test2() throws Exception {
Listlist = new ArrayList();
list.add("a");
list.add(1)//1
}
引入泛型后,我們規定這個容器中只能存放類型為字符串類型的對象,好的,編譯器可以識別泛型并幫我們檢查編譯錯誤,上面的代碼中1處會出現編譯錯誤。注意: 泛型信息僅僅存在于編譯期間,編譯器可以通過泛型信息來對代碼是否存在違規行為(編譯錯誤)來進行檢查,當編譯器將代碼編譯為字節碼之后,泛型信息將不復存在,然而對象的運行時信息仍然是有的,這就解釋了為什么會出現ClassCastException。
別高興太早
有了泛型我們可以讓代碼安全地通過編譯,并且我們認為他是安全的了,嘿嘿,是否就真的安全了呢?是否就能和ClassCastException說拜拜了呢?答案是:NO。看看下面這段代碼:
@Test
public void test3() throws Exception {
Listlist = new ArrayList();
list.add("a");
list.add("b");
List_list = list;
ListintegerList = _list;
for (Integer item : integerList) {
System.out.println(String.format("item[%s]", item));
}
}
上面這段代碼編譯沒有問題,我們沒有直接將泛型參數為String的容器賦值給泛型參數為Integer的容器,而是花了點點小心思,我們現將list賦值給_list,_list生命為可以存儲任何類型,也就相當于無特定類型,而后我們又把_list賦值給integerList容器,integerList容器被聲明為只能存儲類型為Integer的對象。悲催的是這段代碼在運行的時候報了ClassCastException,很明顯,我們知道在迭代integerList容器中的對象的時候,這些對象是有運行時類型信息的,當帶有String類型信息的對象賦值給Integer的時候顯然就報錯了。這一切看起來似乎沒問題,符合邏輯,但是有一個問題我們還沒有問:為什么會沒有編譯錯誤?
泛型術語
在學習數學的時候我們往往會對一個證明題進行論證,而論證之前我們手上往往會有一些不需要證明的已知定理,下面這些“定理”將被用來直接回答上一節中遺留的問題。
- List被稱作泛型類型。
- List中的E被稱為類型變量或類型參數。
- List被稱為參數化類型。
- List中的String被稱為實際類型參數。
- List中的年typeof。
- List被稱為原始類型。
- 參數化類型可以引用一個原始類型對象,編譯報告警告。
- 原始類型可以引用一個參數化類型對象,編譯報告警告。
由上可知,List integerList = _list;可以通過編譯。
看清本質
經過上面的一些小波折,我們了解一些關于泛型的本質:泛型是給javac編譯器使用的,javac是JAVA的編譯器,而泛型可以讓代碼在編譯期間確定類型安全,比如我們告訴編譯器某個容器只能存儲某種類型的對象,那么編譯器會為我們好好地檢查,確保類型安全,但是安全是相對的,只要我們逃過編譯器,我們就有一百種方法讓代碼ClassCastException(比如反射)。同時編譯之后參數化類型在運行時沒有任何泛型信息,也就是為什么List.class和List.class是同一個東西。除了參數化類型之外,容器中的對象在運行的時候是有類型信息的,也就是為什么會ClassCastExcetion。
為學日溢,為道日損,損之又損,以至于無為。無為而無不為,取天下常事以無事;及其有事,不足以取天下。
來自:http://blog.jobbole.com/108217/