Java泛型(T)與萬用字元?
前言:使用泛型的目的是利用Java編譯機制,在編譯過程中幫我們檢測程式碼中不規範的有可能導致程式錯誤的程式碼。例如,我們都知道list容器可以持有任何型別的資料,所以我們可以把String型別和Integer等等同時可以放入同一個list容器中,但這種做法是極其危險的。在泛型機制中這種操作是編譯不通過,會強制你修改。故幫我們減少了隱藏的bug.
一:泛型 T
1.1 泛型用法
根據泛型使用的位置,即用在類(class),屬性(filed)和方法(method)的不同位置,我把它分別總結如下幾種
泛型類:即在類名後新增泛型標識(<T ....>),表示該class持有的一種型別。
泛型屬性:泛型屬性必須結合泛型類使用,用於接受泛型類持有的型別T
泛型方法:即在方法的返回值前宣告泛型<T extends ***>,該泛型T是對該方法的引數T的一種限定。
備註1:如果泛型T沒有被extends修飾(包含類和方法),我們稱之為無界泛型,如果被extends修飾我們稱之為有界泛型如下。
備註2:如果方法引數中有泛型T,而方法的返回型別前沒有泛型T,該型別不是泛型方法,而是泛型類。
備註3:泛型方法常用在工具類中(即該方法只是一種工具),即與類的例項物件關係(持有的方法無關)。
備註4:當泛型方法中的泛型T與類中的泛型T同名時會產生警報,因為編譯器不確定你要使用那個(方法中一個,類中也一個)持有物件。
1.2 有界泛型
相較於無界泛型(沒有限定型別)<T>的用法,我們可以使用有界泛型<T extends ****>來限定類持有物件的範圍,或泛型方法傳入該方法引數的範圍。以保證業務邏輯的正確執行。
備註1:有界泛型只有上界(extends),沒有下界的用法(相比於萬用字元?)。
1.3 泛型繼承
一行程式碼加兩幅圖帶你體會它與我們介面,抽象類和類的區別
ArrayList<String> arrayList = new ArrayList<>(); Object object = new Object(); //The method add(String) in the type ArrayList<String> is not applicable for the arguments (Object) arrayList.add(object);//因為 ArrayList<String>不是 ArrayList<Object>的子類 。
二:萬用字元?
這是一段java官方對萬用字元的定義,In generic code, the question mark (?), called the wildcard, represents an unknown type. The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific). The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype.從這裡我們可以看出萬用字元?是一種未知的型別。
個人小結:常用在方法上(注意與泛型方法的區別,其不需要再方法的返回型別前宣告)
2.1 上界通配
即定義萬用字元的上界,用關鍵字extends宣告,例如
public static void process(List <?extends Foo> list){/ * ... * /}
2.2 無界通配
即不限制萬用字元的界限,不需要任何關鍵字修飾‘?’,例如
public static void printList(List <?> list){/*........*/}
說明其功能形式 public static void printList(List <Object> list){/*........*/},但還是有區別的。
注意:List <Object>和List <?>是不一樣的。List <Object> 可以插入Object,或任何Object物件的子類,成列表<物件>。但是你只能在List <?>中插入null()。
public class TestWildcard { public void printList(List<String> list) { for (Object elem: list) System.out.print(elem + " "); System.out.println(); } public void printList2(List<?> list) { for (Object elem: list) System.out.print(elem + " "); System.out.println(); } public static void main(String[] args) { TestWildcard testWildcard = new TestWildcard(); ArrayList<? extends Object> arrayList = new ArrayList<>(); ArrayList<Object> arrayList2 = new ArrayList<>(); arrayList.add(null); //arrayList.add(testWildcard); arrayList2.add(null); arrayList2.add("2"); List<Integer> li = Arrays.asList(1, 2, 3); List<String> ls = Arrays.asList("one", "two", "three"); testWildcard.printList2(li); testWildcard.printList2(ls); //testWildcard.printList(li);報錯 testWildcard.printList(ls); } }
2.3 下屆通配
即定義萬用字元的下super界,用關鍵字extends宣告,例如
public static void addNumbers(List <?super Integer> list){}
2.4 通配子類
The common parent is List<?>. A hierarchy of several generic List class declarations.
2.5 通配捕獲與輔助方法
通配捕獲:即操作萬用字元?引數 會丟擲異常,除null外,例如:
public class WildcardError { void foo(List<?> i) { i.set(0, i.get(0)); } }
分析:根據編譯器推斷,?是一個Object型別(故可以遍歷出?所代表的物件),但如果要操作List<?>物件,編譯器會要求?代表的具體型別,而編譯器通過現有的規則(真對 ?的規則)是不允許的,故會包錯。
解決捕獲輔助方法
public class WildcardFixed { void foo(List<?> i) { fooHelper(i); } // Helper method created so that the wildcard can be captured // through type inference. private <T> void fooHelper(List<T> l) { l.set(0, l.get(0)); } }
通過一個輔助方法 *** fooHelper(List<T> l){}就解決了
分析:根據規則(真對T),編譯器就知道T的具體型別,故可以安全的操作該物件。
三:小結
泛型與萬用字元區別:最根本的區別就是,java編譯器,把T(泛型)推斷成具體型別,而把萬用字元?推斷成未知型別。而java編輯器只能操作具體型別,不能操作未知型別。導致如果有對引數有修改的操作就必須要使用泛型,如果僅是檢視就可以使用萬用字元.
利用以上推斷,我們可以利用萬用字元特性設計出安全的介面,比如我在一個介面的方法定義了萬用字元引數,則繼承該介面的所有方法,都不能修改該方法傳遞過來的引數。
例如:
public interface GInterface { <T> void foo(List<? extends T> list); } public class GIterfaceImpl implements GInterface{ @Override public <T> void foo(List<? extends T> list) { /** * 只能遍歷list,不能修改list */ for (T t : list) { System.out.println(t); } //list.add(new Object()); } public static void main(String[] args) { GIterfaceImpl gIterfaceImpl = new GIterfaceImpl(); ArrayList<String> list = new ArrayList<>(); list.add("1"); gIterfaceImpl.foo(list); } }
四:延伸
泛型與java8:
參考資料:https://docs.oracle.com/javase/tutorial/java/generics/index.