### 一、泛型的理解與簡單使用
泛型是Java SE 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。這種參數類型可以用在類、接口和方法的創建中,分別稱為泛型類、泛型接口、泛型方法。 Java語言引入泛型的好處是安全簡單。
在Java SE 1.5之前,沒有泛型的情況的下,通過對類型Object的引用來實現參數的“任意化”,“任意化”帶來的缺點是要做顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型可以預知的情況下進行的。對于強制類型轉換錯誤的情況,編譯器可能不提示錯誤,在運行的時候才出現異常,這是一個安全隱患。
泛型的好處是在編譯的時候檢查類型安全,并且所有的強制轉換都是自動和隱式的,以提高代碼的重用率。
**1.1、泛型在接口上的使用:**
~~~
package com.luo.test;
public interface TestInterface<T> {
String objectToString(T o);
}
~~~
對應實現類可以這樣:
~~~
package com.luo.test;
public class TestInterfaceImpl<T> implements TestInterface<T> {
public String objectToString(T o) {
return o.toString();
}
public static void main(String args[]){
TestInterfaceImpl<Integer> testInterfaceImpl = new TestInterfaceImpl<Integer>();
Integer integer = new Integer(123);
System.out.println(testInterfaceImpl.objectToString(integer));
}
}
~~~
運行結果:123
**1.2、泛型在類上單獨使用(不實現接口):**
~~~
package com.luo.test;
public class ClassTest<T> {
private T ob; // 定義泛型成員變量
public T getOb() {
return ob;
}
public void setOb(T ob) {
this.ob = ob;
}
public void showObType() {
System.out.println("T的實際類型是: " + ob.getClass().getName());
}
public static void main(String args[]){
ClassTest<Integer> classTest = new ClassTest<Integer>();
classTest.setOb(123);
classTest.showObType();
}
}
~~~
運行結果:T的實際類型是: java.lang.Integer
**1.3、泛型在方法上單獨使用:**
例如想要實現:
~~~
public List<String> ArrayToList(String[] array);
public List<Double> ArrayToList(Double[] array);
~~~
那么你可以使用泛型如下:
~~~
package com.luo.test;
import java.util.ArrayList;
import java.util.List;
public class MyTest {
public <T> List<T> write(T[] array){
List<T> list = new ArrayList<T>();
for (int i = 0; i < array.length; i++) {
list.add(array[i]);
}
return list;
}
public static void main(String args[]){
}
}
~~~
### 二、泛型的高級使用
**2.1、通配符“?”**
先看如下代碼:

我們都知道,Object所有類的基類,但是需要注意的是
~~~
Collection<Object>并不是所有集合的超類。
List<Object>, List<String>是兩種不同的類型,他們之間沒有繼承關系,即使String繼承了Object。
這就是泛型的強大之處,引入范型后,一個復雜類型如(List),就可以在細分成更多的類型。
~~~
為解決上面代碼報錯問題,引入通配符“?”:

這樣就不會編譯出錯啦。這里使用了通配符“?”指定可以使用任何類型的集合作為參數。
**2.2、邊界通配符“?extends”**
假定有一個畫圖的應用,可以畫各種形狀的圖形,如矩形和圓形等。為了在程序里面表示,定義如下的類層次:
~~~
public abstract class Shape {
public abstract void draw(Canvas c);
}
public class Circle extends Shape {
private int x,y,radius;
public void draw(Canvas c) { ... }
}
public class Rectangle extends Shape
private int x,y,width,height;
public void draw(Canvas c) { ... }
}
~~~
~~~
為了畫出集合中所有的形狀,我們可以定義一個函數,該函數接受帶有泛型的集合類對象作為參數。但是不幸的是,我們只能接收元素類型為Shape的List對象,而不能接收類型為List<Cycle>的對象,這在前面已經說過。為了解決這個問題,所以有了邊界通配符的概念。這里可以采用public void drawAll(List<? extends Shape> shapes)來滿足條件,這樣就可以接收元素類型為Shape子類型的列表作為參數了。
~~~
~~~
//使用邊界通配符的版本
public void drawAll(List<?exends Shape> shapes) {
for (Shapes:shapes) {
s.draw(this);
}
}
~~~
這里就又有個問題要注意了,如果我們希望在List<?exends Shape> shapes中加入一個矩形對象,如下所示:
shapes.add(0, new Rectangle()); //compile-time error
那么這時會出現一個編譯時錯誤,原因在于:我們只知道shapes中的元素時Shape類型的子類型,具體是什么子類型我們并不清楚,所以我們不能往shapes中加入任何類型的對象。不過我們在取出其中對象時,可以使用Shape類型來取值,因為雖然我們不知道列表中的元素類型具體是什么類型,但是我們肯定的是它一定是Shape類的子類型。
為解決添加問題,引入通配符“?super”
**2.3、通配符“?super”**
~~~
List<Shape> shapes = new ArrayList<Shape>();
List<? super Cicle> cicleSupers = shapes;
cicleSupers.add(new Cicle()); //OK, subclass of Cicle also OK
cicleSupers.add(new Shape()); //ERROR
~~~
這表示cicleSupers列表存儲的元素為Cicle的超類,因此我們可以往其中加入Cicle對象或者Cicle的子類對象,但是不能加入Shape對象。這里的原因在于列表cicleSupers存儲的元素類型為Cicle的超類,但是具體是Cicle的什么超類并不清楚。但是我們可以確定的是只要是Cicle或者Circle的子類,則一定是與該元素類別兼容。
**2.4、通配符總結**
如果你想從一個數據類型里獲取數據,使用 ? extends 通配符
如果你想把對象寫入一個數據結構里,使用 ? super 通配符
如果你既想存,又想取,那就別用通配符。
### 三、總結
**3.1、 類型擦除概念**
類型擦除指的是通過類型參數合并,將泛型類型實例關聯到同一份字節碼上。編譯器只為泛型類型生成一份字節碼,并將其實例關聯到這份字節碼上,因此泛型類型中的靜態變量是所有實例共享的。此外,需要注意的是,一個static方法,無法訪問泛型類的類型參數,因為類還沒有實例化,所以,若static方法需要使用泛型能力,必須使其成為泛型方法。類型擦除的關鍵在于從泛型類型中清除類型參數的相關信息,并且再必要的時候添加類型檢查和類型轉換的方法。在使用泛型時,任何具體的類型都被擦除,唯一知道的是你在使用一個對象。比如:List和List在運行事實上是相同的類型。他們都被擦除成他們的原生類型,即List。因為編譯的時候會有類型擦除,所以不能通過同一個泛型類的實例來區分方法,如下面的例子編譯時會出錯,因為類型擦除后,兩個方法都是List類型的參數,因此并不能根據泛型類的類型來區分方法。
~~~
/*會導致編譯時錯誤*/
public class Erasure{
public void test(List<String> ls){
System.out.println("Sting");
}
public void test(List<Integer> li){
System.out.println("Integer");
}
}
~~~
那么這就有個問題了,既然在編譯的時候會在方法和類中擦除實際類型的信息,那么在返回對象時又是如何知道其具體類型的呢?如List編譯后會擦除掉String信息,那么在運行時通過迭代器返回List中的對象時,又是如何知道List中存儲的是String類型對象呢?
擦除在方法體中移除了類型信息,所以在運行時的問題就是邊界:即對象進入和離開方法的地點,這正是編譯器在編譯期執行類型檢查并插入轉型代碼的地點。泛型中的所有動作都發生在邊界處:對傳遞進來的值進行額外的編譯期檢查,并插入對傳遞出去的值的轉型。
**3.2、方法重載**
在JAVA里面方法重載是不能通過返回值類型來區分的,比如代碼一中一個類中定義兩個如下的方法是不容許的。但是當參數為泛型類型時,卻是可以的。如下面代碼二中所示,雖然形參經過類型擦除后都為List類型,但是返回類型不同,這是可以的。
~~~
/*代碼一:編譯時錯誤*/
public class Erasure{
public void test(int i){
System.out.println("Sting");
}
public int test(int i){
System.out.println("Integer");
}
}
~~~
~~~
/*代碼二:正確 */
public class Erasure{
public void test(List<String> ls){
System.out.println("Sting");
}
public int test(List<Integer> li){
System.out.println("Integer");
}
}
~~~
**3.3、泛型類型是被所有調用共享的**
所有泛型類的實例都共享同一個運行時類,類型參數信息會在編譯時被擦除。因此考慮如下代碼,雖然ArrayList和ArrayList類型參數不同,但是他們都共享ArrayList類,所以結果會是true。
~~~
List<String>l1 = new ArrayList<String>();
List<Integer>l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass()); //True
~~~
**3.4、instanceof**
不能對確切的泛型類型使用instanceOf操作。如下面的操作是非法的,編譯時會出錯。
~~~
Collection cs = new ArrayList<String>();
if (cs instanceof Collection<String>){…}// compile error.如果改成instanceof Collection<?>則不會出錯。
~~~
**3.5、泛型數組問題**
不能創建一個確切泛型類型的數組。如下面代碼會出錯。
~~~
List<String>[] lsa = new ArrayList<String>[10]; //compile error.
~~~
因為如果可以這樣,那么考慮如下代碼,會導致運行時錯誤。
~~~
List<String>[] lsa = new ArrayList<String>[10]; // 實際上并不允許這樣創建數組
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer>li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li;// unsound, but passes run time store check
String s = lsa[1].get(0); //run-time error - ClassCastException
~~~
因此只能創建帶通配符的泛型數組,如下面例子所示,這回可以通過編譯,但是在倒數第二行代碼中必須顯式的轉型才行,即便如此,最后還是會拋出類型轉換異常,因為存儲在lsa中的是List<Integer>類型的對象,而不是List<String>類型。最后一行代碼是正確的,類型匹配,不會拋出異常。
~~~
List<?>[] lsa = new List<?>[10]; // ok, array of unbounded wildcard type
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer>li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; //correct
String s = (String) lsa[1].get(0);// run time error, but cast is explicit
Integer it = (Integer)lsa[1].get(0); // OK
~~~
參考文章:
[http://qiemengdao.iteye.com/blog/1525624](http://qiemengdao.iteye.com/blog/1525624)
- 前言
- Java生成中間logo的二維碼(還可以加上二維碼名稱哦)
- Java我的高效編程之常用函數
- AES加密解密&&SHA1、SHA加密&&MD5加密
- Java中synchronized的使用實例
- Java基礎之集合
- Java基礎之泛型
- Java基礎之枚舉妙用
- 那些年用過的一些前端框架
- 關于正則,那些年一直存在的疑惑解答(正則菜鳥不容錯過)
- 給pdf文件添加防偽水印logo(附工程源碼下載)
- SpringMVC+BUI實現文件上傳(附詳解,源碼下載)
- Java異常封裝(自己定義錯誤碼和描述,附源碼)
- javaweb異常提示信息統一處理(使用springmvc,附源碼)
- 關于Java,那些我心存疑惑的事(不斷更新中...)
- 深入Java虛擬機(1)——Java體系結構
- 深入Java虛擬機(2)——Java的平臺無關性
- 深入Java虛擬機(3)——安全
- 深入Java虛擬機(4)——網絡移動性
- Linux文件編輯命令詳細整理
- 阿里云服務器云數據庫免費體驗(Java Web詳細實例)
- 項目部署、配置、查錯常用到的Linux命令
- Shell腳本了解
- Ajax原理學習
- linux下安裝apache(httpd-2.4.3版本)各種坑
- JSP九大內置對象
- Servlet再度學習
- 開發人員系統功能設計常用辦公軟件分享
- java.lang.ClassNotFoundException:org.springframework.web.context.ContextLoaderListener問題解決
- tomcat內存溢出解決,java.lang.OutOfMemoryError: PermGen space
- 《Java多線程編程核心技術》推薦
- 關于跳槽,是我心浮氣躁?還是我確實該離開了?
- Java I/O學習(附實例和詳解)
- Java經典設計模式之五大創建型模式(附實例和詳解)
- Java經典設計模式之七大結構型模式(附實例和詳解)
- Java經典設計模式之十一種行為型模式(附實例和詳解)
- Java內存管理
- SQL實例整理
- 數據庫面試常問的一些基本概念