什麼是泛型
那麼什麼是泛型呢?
我們經常在java裏見到泛型的地方,應該就是那些集合類了吧。
List<String> list = new ArrayList<String>();
差不多是這樣。在java7及其之後 由於編譯器會做類型推薦那麼也可以簡寫成這樣
List<String> list = new ArrayList<>();
大多數人對泛型的理解都是通過這樣的集合類。這也就是泛型的一個基本理解。 類的類型的參數化,就是泛型。 泛型可以定義在類,接口,方法中。
常用的 T,E,K,V,?
本質上這些個都是通配符,沒啥區別,只不過是編碼時的一種約定俗成的東西。比如上述代碼中的 T ,我們可以換成 A-Z 之間的任何一個 字母都可以,並不會影響程序的正常運行,但是如果換成其他的字母代替 T ,在可讀性上可能會弱一些 。
泛型有三種實現方式,分別是泛型接口、泛型類、泛型方法,下面通過泛型方法來介紹什麼是類型參數。
泛型方法聲明方式:訪問修飾符 <T,K,S…> 返回類型 方法名(方法參數){方法體}
-
訪問修飾符與返回類型中間有個<T,K,S…>,T、K、S等屬於類型參數,可以隨便定義。
-
返回類型和方法參數可以是或者包含類型參數T、K、S等。
-
可以限定類型參數必須實現某些接口或者繼承某個類,多個限定的類、接口中間用&分隔,類必須放在限定列表中所有接口的前面。
-
泛型方法可以定義在普通類中。
? 無界通配符
類型通配符一般是使用?代替具體的類型參數。
List<?> list=new ArrayList<>();
這種只能傳遞null類型 方法返回的類型只能賦給Object
有什麼作用呢?對於一些不需要實際類型的方法,就顯得比泛型方法可讀性強,如下。
public static void method1(List<?> operate) {
System.out.println(operate.get(0) == null);
}
public static <T> void method2(List<T> operate) {
System.out.println(operate.get(0) == null);
}
上界通配符 < ? extends E>
但是你有沒有仔細想一下以下這種情況。假設有兩個類Parent
和 Children
其關係爲
Children extends Parent
Parent
就是Children
的父類
List<Parent> list = new ArrayList<Children>();
這樣顯然是不通過的。其實呢這個是泛型的一種性質,叫做covariance
就是 協變 的意思。怎麼去解釋呢 ,協變可以理解爲 你聲明一個父類的List 讓後你賦值給他一個子類的List按道理說也是可以的。
但是呢java中的泛型是不支持這種性質的,其實java這樣限制也不是不無道理,原因是java類型在編譯的時候會做類型擦除,就是爲了保證類型的安全,所以java對泛型纔會有這種限制。另外還有一點就是數組:
Parent[] arr = new Children[10];
數組在編譯的時候是沒有做類型擦除的,所以他支持這種特性。
總之java不允許你把一個子類類型的對象賦值給一個父類的泛型類型的聲明。
就像上面所說的,java的泛型有這種限制但是在一些特殊場景的時候,我們還是需要這種特性,所以java提供了響應的語法,來解除這種限制
我們只需要在父類型的泛型中加入 ? extends
就可以解除這種限制
List<? extends Parent> list = new ArrayList<Children>();
這種解除的寫法,雖然解除了賦值的限制,但是由於java要保證你的類型安全,會給你增加另外一個限制。
List<? extends Parent> list = new ArrayList<Children>();
Children children = new Children();
list.add(children);
你會發現我們無法時候使用引用對象 參數包含類型參數(E)
的方法
boolean add(E e);
也不能給他包含類型參數(E)
的字段複製。
上屆:用 extends 關鍵字聲明,表示參數化的類型可能是所指定的類型,或者是此類型的子類。
說白了你就只能用它不能修改它。所以當你遇到只想使用不像修改的情況下,擴大參數的接收範圍,讓程序更加靈活,如以下情況:
public static void main(String[] args) {
List<Children> childrenList = new ArrayList<>();
List<Parent> parentList = new ArrayList<>();
printLine(childrenList);
printLine(parentList);
}
private static void printLine(List<? extends Parent> list) {
list.forEach(System.out::println);
}
下界通配符 < ? super E>
與? extend
相對的呢還有一個 ? super
他也是可以使變量或者參數的接收範圍擴大,不過擴大的意義跟? extend
截然相反他可以讓你 吧父類的泛型泛型類型聲明,賦值給子類的類型泛型聲明。
List<? super Children> list = new ArrayList<Parent>();
這種性質跟之前講到的 協變 一樣也有一個名字 叫做 contravariant
叫做 逆變 也可以叫做反變,不過跟? extend
一樣他在解除限制的同時,也帶來了一些使用時候的附加限制,你在使用這種引用類型的時候 不能調用返回值包含類型參數的方法
List<? super Children> list = new ArrayList<Parent>();
Children children = list.get(0);
也不能獲得包含類型參數(E)
的字段值
E get(int index);
下界: 用 super 進行聲明,表示參數化的類型可能是所指定的類型,或者是此類型的父類型,直至 Object
舉個例子,我又一個parent
的List 我在處理的時候希望插入一個 children
的對象,這時候呢就可以使用 ? super
來擴大參數的接收範圍,這樣就可以啦。
public static void main(String[] args) {
List<Parent> list = new ArrayList<>();
addParent(list);
}
private static void addParent(List<? super Children> list) {
Children children = new Children();
list.add(children);
}
PECS 法則
在得到這種擴大參數的支持的同時,這些變量和方法參數會受到一些 只能使用而不能修改 或者 只能修改而不能使用 的限制,這種限制呢也成爲 PECS法則。
PECS指“Producer Extends,Consumer Super”
換句話說,如果參數化類型表示一個生產者,就使用? extends T
如果它表示一個消費者,就使用? super T
。
總結
總之在學一門語言的時候泛型及其的重要,能更好的規範代碼之前值的流通,你的代碼也就變的更代碼了。