泛型是什麼? 泛型本質是指類型參數化。意思是允許在定義類、介面、方法時使用類型形參,當使用時指定具體類型,所有使用該泛型參數的地方都被統一化,保證類型一致。如果未指定具體類型,預設是Object類型。集合體系中的所有類都增加了泛型,泛型也主要用在集合。 泛型的定義 泛型類:public class ...
泛型是什麼?
泛型本質是指類型參數化。意思是允許在定義類、介面、方法時使用類型形參,當使用時指定具體類型,所有使用該泛型參數的地方都被統一化,保證類型一致。如果未指定具體類型,預設是Object類型。集合體系中的所有類都增加了泛型,泛型也主要用在集合。泛型的定義
泛型類:public class Demo<T> {} ,T表示未知類型。 泛型介面:public interface ImplDemo<T,V>{} ,和定義類一樣(介面就是一個特殊類)。 泛型方法:public <T> void demo1(T name){System.out.println(name);} , public <T> T demo2(T t){ return t;}泛型的好處
- 編譯時確定類型,保證類型安全,避免類型轉換異常。
- 避免了強制類型轉換。
- 代碼利於重用,增加通用性。
泛型的限制和規則
- 泛型的類型參數只能是引用類型,不能使用值類型。
- 泛型的類型參數可以有多個。
- 泛型類不是真正存在的類,不能使用instanceof運算符。
- 泛型類的類型參數不能用在靜態申明。
- 如果定義了泛型,不指定具體類型,泛型預設指定為Ojbect類型。
- 泛型使用?作為類型通配符,表示未知類型,可以匹配任何類型。因為是未知,所以無法添加元素。
- 類型通配符上限:<? extends T>,?代表是T類型本身或者是T的子類型。常用於泛型方法,避免類型轉換。
- 類型通配符下限。<? super T>,?代表T類型本身或者是T的父類型。
- 除了通配符可以實現限制,類、介面和方法中定義的泛型參數也能限制上限和下限。
泛型代碼實例
接下來,使用代碼來演示泛型的用途,建議使用目錄查看具體內容。集合類演示泛型
//未指定泛型 TreeSet ts = new TreeSet(); ts.add(10); ts.add(25); ts.add("30"); System.out.println(ts);//運行時報錯,類型轉換異常 //mode 2 TreeSet<Integer> ts2 = new TreeSet<>(); ts2.add(10); ts2.add(25); ts2.add("30"); //編譯器提示報錯,無法添加非Integer類型
未使用泛型時,可以添加任意元素,因為TreeSet會比較每一個元素,所以運行時會引發類型轉換異常。使用泛型後,只能添加同一個類型,所以不會出錯。
定義泛型類
public class Person<T> { private T name;//類型是未知的 public Person(T name) { this.name = name; } public T getName() { return name; } public void sexName(T name) { this.name = name; } }在上面實例中,Person類定義成泛型類,成員變數name的類型指定為T,表示未知類型。實例化該類對象後,可以看到name的類型是Object,表示可以接收任何類型。
Person p = new Person(10); //new Person(Object name)加上泛型後
//使用泛型兩種方式 Person<String> ps = new Person<String>(); //new Person<String>(String name) Person<String> ps = new Person<>();//new Person<>(T name)第一種,會直接確定參數類型是什麼,而第二種的參數類型是T ,但如果加入的非String類型,編譯器會檢查並報錯。兩者區別不大。在JDK1.7之後使用第二種,會自動檢查泛型,可以省略後部分<>的泛型參數,建議使用第二種。
定義泛型介面
interface A<T>{ void display(T value); T getValue(T v); } //未對泛型介面指定具體類型 public class Person implements A{ @Override public void display(Object obj) { System.out.println(); } @Override public Object getValue(Object v) { return null; } }如果我們定義了泛型,不指定具體類型,預設就是Object類型。當我們為泛型介面指定具體類型後,代碼如下:
//泛型介面 interface A<T>{ void display(T value); T getValue(T v); } //為泛型介面指定具體類型 public class Person implements A<String>{ @Override public void display(String value) { } @Override public String getValue(String v) { return null; } }泛型介面指定具體類型後,所有使用了該泛型參數的地方都被統一化。其實泛型介面和泛型類是一樣的寫法。
定義泛型方法
先使用常規方法進行對比。
public static void main(String[] args) { int[] arr = new int[] {1, 8, 15, 6, 3}; double[] douArr = {10.5, 25.1, 4.9, 1.8}; String[] strArr = {"我","是","字","符","串"}; forArr(strArr); } //遍曆數組的重載方法,支持int和double類型 public static void forArr(int[] arr) { for(int i=0; i<arr.length; i++) { System.out.println(arr[i]); } } //重載了 public static void forArr(double[] arr) { for(double d : arr) { System.out.println(d); } } //…… //……
如上所示,如果想遍歷Stirng類型數組,那就還要再次重載代碼,如果是八種類型都有,代碼量非常龐大。使用泛型方法全部通用,代碼如下:
public static void main(String[] args) { Integer[] arr = {1, 8, 15, 6, 3}; Double[] douArr = {10.5, 25.1, 4.9, 1.8}; String[] strArr = {"我","是","字","符","串"}; forArrGenric(strArr); } //泛型方法 public static <T> void forArrGenric(T[] arr) { for(int i=0; i < arr.length; i++) { System.out.println(arr[i]); } }只需定義一個泛型方法,根據運行時傳入的參數類型,動態地獲取類型,就能做到遍歷所有類型數組。但需要註意,泛型的類型參數只能是引用類型,值類型無法在泛型中使用,所以上面的數組都改成了引用類型。值類型需要使用對應的包裝類類型。
使用類型通配符
使用之前,先使用常規方式來進行比較。public static void main(String[] args) { HashSet hs = new HashSet(); hs.add("A"); hs.add("QQ"); hs.add("Alipay"); new Test2().test2(hs); } //普通遍歷Set集合,Set是泛型介面,沒指定具體泛型參數會引起警告 public void test(Set s) { for(Object o : s) System.out.println(o); } //增加泛型參數,參數類型是Set<Object> public void test2(Set<Object> s) { for(Object o : s) System.out.println(o); }方法參數的Set集合使用了泛型參數<Object>,方便將參數類型轉換成Object,看起來沒什麼錯。當傳入一個帶泛型參數的集合時,會出現編譯錯誤。代碼如下:
public static void main(String[] args) { HashSet<String> hs = new HashSet(); hs.add("A"); hs.add("QQ"); hs.add("Alipay"); new Test2().test2(hs); //error } //增加泛型參數,參數類型是Set<Object> public void test2(Set<Object> s) { for(Object o : s) System.out.println(o); }因為泛型類不是真正存在的類,所以Set<String>和Set<Object>不存在關係,自然無法作為參數傳入進去。這時我們就可以使用類型通配符,如下:
//使用類型通配符作為類型參數 public void test2(Set<?> s) { for(Object o : s) System.out.println(o); }
Set<?>表示可以匹配任意的泛型Set。雖然可以使用各種泛型Set了。但弊端就是類型未知,所以無法添加元素。還有範圍過於廣泛,所以這時可以考慮限制的類型通配符。
限制的類型通配符
上面代碼只要是泛型Set都允許被遍歷,如果只想類型通配符表示一個類和其子類本身呢?設置類型通配符上限,代碼如下:
public class Test2 { public static void main(String[] args) { ArrayList<Test2> ar = new ArrayList<>(); List<Test3> lt = new ArrayList<>(); List<String> lStr = new ArrayList<>(); demo(ar); demo(lt); demo(lStr); //error } //限制的類型通配符 public static void demo(List<? extends Test2> t) { for(int i = 0; i < t.size(); i++) { System.out.println(t.get(i)); } } } class Test3 extends Test2{}//子類<? extends T>:表示類型是T本身或者是T類型的子類類型。
<? super T>:表示類型是T類型本身或者是T類型的父類類型。叫做類型通配符的下限。使用方式都差不多。
泛型為何不能應用於靜態申明的實例解析
先給一個例子,在靜態變數中和靜態代碼塊中使用泛型。public class Test<T> { public static T name; //error public T sex ; static { T ab; //error } }報出異常:不能使一個靜態引用指向一個非靜態的類型 T。靜態和非靜態之分就在於靜態是編譯時類型,動態是運行時類型。T代表未知類型,如果可以用於靜態申明,因為是未知類型,系統沒法指定初始值,手動賦值也不行,因為不知道啥類型,只有運行時才可以指定。而泛型存在的意義就是為了動態指定具體類型,增強靈活性和通用性,所以用於靜態聲明違背了使用原則。為什麼實例變數和實例方法可以使用呢?因為當你使用實例變數或者方法時,就說明對象存在了,即代表泛型參數也指定了。未指定具體類型預設是Object類型。
為什麼靜態方法中可以定義泛型方法呢?
先給三個實例,我們來慢慢分析。public class Test<T> { public static void main(String[] args) { } //泛型方法 public T demo1(T t) { return t; } //靜態方法使用泛型參數 // public static T demo2(T t) { return t;} //定義泛型靜態方法 public static <W> void demo3(W w) { System.out.println(w); } }首先,要明確一點,泛型作用是確定具體類型。先看一個泛型方法,使用了泛型參數T作為返回值,當使用對象時來調用該方法時,T類型會變成具體類型。第二個泛型方法是靜態的,使用了T作為返回值和方法參數類型,但是靜態方法是屬於類的,類直接調用的話,T類型無法指定具體類型,那麼該方法就沒有意義。所以直接報錯。第三個也是靜態方法,但是該靜態方法是自定義一個泛型參數,並非使用類型參數。所以當傳入一個具體類型時,該靜態方法的<W>就是具體類型了。兩者靜態方法的區別就是一個是使用泛型參數,一個是定義泛型方法。