泛型是Java1.5中出現的新特性,也是最重要的一個特性。泛型的本質是參數化類型,也就是說所操做的數據類型被指定爲一個參數。這種參數類型能夠用在類、接口和方法的建立中,分別稱爲泛型類、泛型接口、泛型方法。這個類型參數將在程序運行時肯定。java
咱們能夠把泛型理解爲做用在類或者接口上面的標籤。根據這個標籤的類型傳入規定的數據類型,不然就會出錯,其中類型必須是類類型,不能是基本數據類型。例如咱們國家中醫存藥的箱子,每一個箱子上面都貼有一個標籤,若是上面貼的是冬蟲夏草,那麼就只能放冬蟲夏草,而和其餘的藥物混合放在一塊兒就很是的亂,很容易出現錯誤。安全
泛型就是這樣的道理,咱們先來看下泛型最簡單的使用吧:ide
ArrayList<String> list=new ArrayList<>(); list.add("Hello"); //只能放字符串,若是放數字編譯報錯 //list.add(666);
注意:Java1.7以後泛型能夠簡化,就是變量前面的參數類型必需要寫,然後面的參數類型能夠寫出來,也能夠省略不寫。性能
簡單舉個例子,這個應該是網上最經典的例子:學習
//建立集合對象 ArrayList list=new ArrayList(); list.add("Hello"); list.add("World"); list.add(111); list.add('a'); //遍歷集合內容 for (int i = 0; i < list.size(); i++) { String str= (String) list.get(i); System.out.println(str); }
上面程序運行結果毫無疑問會出現異常java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String,這就是沒有泛型的弊端。由於上面的ArrayList中就能夠存聽任意類型(即Object類型),咱們知道,全部的類型均可以用Object類型來表示,而Object在類型轉換方面很容易出現錯誤。測試
也許有人會想能夠先用Object類型代替String接收,而後再轉成相對應的數據類型,這樣是也能夠的,可是萬一在轉換的時候某個數據類型看錯了或者記錯了,那麼還不是會出現轉換異常。就算運氣好所有都對了,可是這種強制類型轉換一大堆的代碼,你的同事或者領導看了可能會拿刀來砍死你,很是不利於代碼後期的維護。this
可見沒有泛型是萬萬不能的,同時咱們還能得出泛型帶來的一些好處:spa
①、能夠有效的防止類型轉換異常的出現。.net
②、可讓代碼更簡潔,從而提升代碼的可讀性、可維護性和穩定性。3d
③、能夠加強for循環遍歷集合,進而提高性能,由於都是相同類型的。
④、能夠解決類型安全編譯警告。由於沒有泛型時全部類型向上轉型爲Object類型,因此存在類型安全問題。
泛型它有三種使用方式,分別爲:泛型類、泛型接口、泛型方法。咱們接下來學習它們怎麼使用。
泛型類就是把泛型定義在類上,它的使用比較的簡單。咱們先來定義一個最普通的泛型類:
//定義泛型類,其中T表示一個泛型標識 public class Generic<T> { private T key; public Generic() { } public Generic(T key) { this.key = key; } //這裏不是泛型方法,它們是有區別的 public T getKey() { return key; } public void setKey(T key) { this.key = key; } }
泛型類的測試代碼以下,咱們只需在類實例化的時候傳入想要的類型,而後泛型類中的T就會自動轉換成該相應的類型。
public static void main(String[] args) { //有參構造器初始化,傳入String類型 Generic<String> generic = new Generic<>("Generic1..."); System.out.println(generic.getKey());//Generic1... //無參構造器初始化,傳入Integer類型 Generic<Integer> generic1 = new Generic<>(); generic1.setKey(123456); int key = generic1.getKey(); System.out.println(key);//123456 }
而後咱們再來看下泛型中泛型標識符,在上面這個泛型類中 T 表示的是任意類型,泛型中還有不少這樣的標識,例如K表示鍵,V表示值,E表示集合,N表示數子類型等等。
在泛型類中還有兩種特殊的使用方式,是關於繼承的時候是否傳入具體的參數。
①、當繼承泛型類的子類明確傳入泛型實參時:
//子類中明確傳入泛型參數類型 class SubGeneric extends Generic<String>{ } //測試類 class Test{ public static void main(String[] args) { SubGeneric subGeneric = new SubGeneric(); //調用繼承自父類的屬性 subGeneric.setKey("SubGeneric..."); String key = subGeneric.getKey(); System.out.println(key); } }
能夠得出子類在繼承了帶泛型的父類時,明確的指明瞭傳入的參數類型,那麼子類在實例化時,不須要再指明泛型,用的是父類的類型。
②、當繼承泛型類的子類沒有明確傳入泛型實參時:
//子類沒有明確傳入泛型參數類型 class SubGeneric<T> extends Generic<T>{ private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } class Test{ public static void main(String[] args) { SubGeneric<Integer> subGeneric = new SubGeneric<>(); //調用繼承自父類的屬性 subGeneric.setKey(123456); int key = subGeneric.getKey(); System.out.println(key); //調用子類本身的屬性 SubGeneric<String> subGeneric1=new SubGeneric<>(); subGeneric1.setValue("SubGeneric..."); String value = subGeneric1.getValue(); System.out.println(value); } }
當子類沒有明確指明傳入的參數類型時,那麼在子類實例化時,都是根據子類中傳入的類型來肯定的父類的類型。若是父類和子類中有一個明確,另外一個沒有明確或者二者明確的類型不同,那麼它們的類型會自動提高爲Object類型。以下:
//一個明確,一個不明確 class SubGeneric<T> extends Generic<String>{ private T value; }
泛型接口和泛型類的定義和使用幾乎相同,只是在語句上面有些不一樣罷了,因此這裏就很少說什麼了。直接來看一下例子:
//定義一個泛型接口 public interface IGeneric<T> { public T show(); } class Test implements IGeneric<String>{ @Override public String show() { return "hello"; } }
泛型接口實現類中是否明確傳入參數和泛型類是同樣的,因此就不說了,能夠參考泛型類。
前面介紹了泛型類和泛型接口,它們二者的使用相對來講比較的簡單,而後咱們再來看一下泛型方法,泛型方法比它們二者稍微複雜一點點。在前面的泛型類中咱們也看到了方法中有用到泛型,它的格式是這樣的:
//這裏不是泛型方法,它們是有區別的 public T getKey() { return key; }
可是它並非泛型方法。泛型方法的中的返回值必須是用 <泛型標識> 來修飾(包括void),只有聲明瞭<T>的方法纔是泛型方法,這裏<T>代表該方法將使用泛型標識T,此時才能夠在方法中使用泛型標識T。而單獨只用一個泛型標識 T 來表示會報錯,系統沒法解析它是什麼。固然咱們也可使用其餘的泛型標識符如:K、V、E、N等。
泛型方法的聲明格式以下:
//這裏的泛型標識 T 與泛型類中的 T 沒有任何關係 public <T> T show(T t){ return t; }
注意:泛型方法的泛型與所屬的類的泛型沒有任何關係。咱們能夠舉例說明:
public class GenericMethod<T> { //這裏的泛型標識 T 與泛型類中的 T 沒有任何關係 public <T> T show(T t){ return t; } } class Test{ public static void main(String[] args) { //實例化泛型類,傳入String類型 GenericMethod<String> genericMethod = new GenericMethod<>(); //調用方法,傳入數字 Integer show = genericMethod.show(123456); System.out.println(show);//123456 //調用方法,傳入字符 Character a = genericMethod.show('a'); System.out.println(a);//a } }
在測試類中,建立類的實例時,泛型傳入的是String類型,而在調用方法的時候,分別傳入了數字類型和字符類型。
至此咱們得出結論:泛型方法是在調用方法的時候指明泛型的具體類型,因此泛型方法能夠是靜態的;泛型類和泛型接口是在實例化類的時候指明泛型的具體類型,因此泛型類和泛型接口中的方法不能是靜態的。
若是某個類繼承了另外一個類,那麼它們之間的轉換就會變得簡單,例如Integer繼承Number:
Number number=123; Integer integer=456; number=integer;//能夠賦值 Number[] numbers=null; Integer[] integers=null; numbers=integers;//能夠賦值
上面這麼作徹底能夠,可是把它們用在泛型中卻不是這麼一回事了。
List<Number> list1=null; List<Integer> list2=null; //編譯報錯,不兼容的類型 //list1=list2;
這是由於Integer繼承自Number類,可是List<Integer>並非繼承自List<Number>的,它們兩是都繼承自Object這個根父類。看到下面這張圖片可能會更好的理解(圖片引用自https://blog.csdn.net/whdalive/article/details/81751200)
這裏用這樣一段話來歸納:雖然類A是類B的父類,可是G<A>和G<B>它們之間不具有任何子父類關係,兩者都是並列關係,惟一的關係就是都繼承自Object這個根父類。
再來看一下另外一種狀況:帶泛型的類(接口)與另外一個類(接口)有繼承(實現)的關係。
舉例:在集合中,ArrayList <E>實現List <E> , List <E>擴展Collection <E> 。 所以ArrayList <String>是List <String>的子類型,List <String>是Collection <String>的子類型。 因此只要不改變類型參數,就會在類型之間保留子類型關係。
Collection<String> collection=null; List<String> list=null; ArrayList<String> arrayList=null; collection=list; list=arrayList;
這裏其實就是普通的繼承(實現),相似於Integer繼承在Number類同樣。圖片以下:
參考文章:https://blog.csdn.net/whdalive/article/details/81751200
爲何要用通配符呢?那確定是泛型在某些地方還不是很是完美,因此纔要用到通配符呀,咱們來分析一下:
在上面的一節中咱們講了Integer是Number的一個子類,而List<Integer>和List<Number>兩者都是並列關係,它們之間毫無關係,惟一的關係就是都繼承自Object這個根父類。那麼問題來了,咱們在使用List<Number>做爲方法的形參時,可否傳入List<Integer>類型的參數做爲實參呢?其實這個時候答案已經很明顯了,是不能的。因此這時候就能夠用到通配符了。
在沒有使用通配符的狀況下咱們的代碼要這樣寫:
public class Generic { public static void main(String[] args) { List<Number> list1=new ArrayList<>(); list1.add(1); list1.add(2); List<Integer> list2=new ArrayList<>(); list2.add(3); list2.add(4); show(list1); //報錯說不能應用Integer類型 //show(list2); } public static void show(List<Number> list){ System.out.println(list.toString());//[1, 2] } }
若是還想要支持Integer類型則須要添加新的方法:
public static void show1(List<Integer> list){ System.out.println(list.toString());//[1, 2] }
這樣就會致使代碼大量的冗餘,很是不利於閱讀。因此此時就須要泛型的通配符了,格式以下:
public static void show(List<?> list){ System.out.println(list.toString());//[1, 2][3, 4] }
當咱們使用了通配符以後,就能輕鬆解決以上問題了。在Java泛型中用 ? 號用來表示通配符,?號通配符表示當前能夠匹配任意類型,任意的Java類均可以匹配。可是在使用通配符以後必定要注意:該對象中的有些方法任然能夠調用,而有些方法則不能調用了。例如:
public static void show(List<?> list){ //list.add(66);//不能使用add() list.add(null);//惟獨能添加的只有null Object remove = list.remove(0);//可使用remove() System.out.println(remove); Object get = list.get(0);//可使用get() System.out.println(get); System.out.println(list.toString()); }
這是由於只有調用的時候才知道List<?>通配符中的具體類型是什麼。因此對於上面的list而言,若是調用add()方法,那麼咱們並不知道要添加什麼樣的類型,因此會報錯。而remove()和get()方法都是根據索引來操做的,它們都是數字類型,因此它是能夠被調用的。
通配符還有兩種擴展的用法:
List<? extends ClassName>,它表示的是傳入的參數必須是ClassName的子類(包括該父類),相似於數學中(∞,ClassName],而後簡單舉例說明:
public static void main(String[] args) { List<Number> list1=new ArrayList<>(); list1.add(1); List<Integer> list2=new ArrayList<>(); list2.add(2); List<Object> list3=new ArrayList<>(); list3.add(3); show(list1);//傳入Number類型 show(list2);//傳入Integer類型 //show(list3);//傳入Object類型,報錯說不能應用Object類型,由於這裏最大隻能用到Number類型 } public static void show(List<? extends Number> list){ System.out.println(list.toString()); }
List<? super ClassName>,它表示的是傳入的參數必須是ClassName的父類(包括該子類),相似於數學中[ClassName,∞)
public static void main(String[] args) { List<Number> list1=new ArrayList<>(); list1.add(1); List<Integer> list2=new ArrayList<>(); list2.add(2); List<Object> list3=new ArrayList<>(); list3.add(3); show(list1);//傳入Number類型 //show(list2);//傳入Integer類型,報錯說不能應用Integer類型,由於此時最小隻能用到Number類型 show(list3);//傳入Object類型, } public static void show(List<? super Number> list){ System.out.println(list.toString()); }