Java基礎 泛型

什麼是泛型

那麼什麼是泛型呢?

我們經常在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>

但是你有沒有仔細想一下以下這種情況。假設有兩個類ParentChildren 其關係爲

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

總結

總之在學一門語言的時候泛型及其的重要,能更好的規範代碼之前值的流通,你的代碼也就變的更代碼了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章