1. 程式人生 > >泛型知識點總結

泛型知識點總結

(1)型別引數是一種佔位符,泛型的擦除保證了泛型程式碼內部無法獲知型別的具體型別,也即無法使用new instanceof等,這也保證不使用泛型的舊版本可以繼續工作! 

(2)泛型型別只有在靜態型別檢查期間才出現,之後所有的泛型型別都將被擦除,替換為他們的非泛型上界,例如List<T>被擦除為List,而普通的型別變數在未指定邊界的情況下將被擦除為Object.  

(3)泛型僅僅是提供給javac編譯器使用的,可以限定集合中的輸入型別,讓編譯器攔截源程式中的非法輸入,編譯器編譯帶型別說明的集合時會去掉型別資訊,對於引數化得泛型型別,getClass()方法的返回值和原始型別完全一樣。
(4)泛型函式允許型別引數被用來表示方法的一個或多個引數之間的依賴關係,或者引數與其返回值的依賴關係。如果沒有這樣的依賴關係,不應該使用泛型方法。

(5)Object[]陣列可以是任何陣列的父類,或者說,任何一個數組都可以向上轉型成它在定義時指定元素型別的父類的陣列,這個時候如果我們往裡面放不同於原始資料型別 但是滿足後來使用的父類型別的話,編譯不會有問題,但是在執行時會檢查加入陣列的物件的型別,於是會拋ArrayStoreException:

  Integer[] integers=new Integer[10];
  Number[] number=integers;
  number[0]=new Double(10.0); //Exception in thread "main" java.lang.ArrayStoreException: java.lang.Double

(6)協變,就是父類和子類保持相同形式的變化,但是協變有時候被支援,有時候不被支援


比如,在
陣列中,協變是支援的
比如 
class Parent{}
class Child extends Parent{}
那麼 Child[]可以
賦值給 Parent[] ,這個就是協變

但是,在
泛型中,協變就不可以
比如 雖然Child extends Parent
但是,假設有個 Test<T>,則 Test<Child>不可以賦值給Test<Parent>,這2者毫無關係

(7)Java語言的泛型基本上完全在編譯器中實現的,由編譯器執行型別檢查和型別推斷,然後生成普通的非泛型的位元組碼。 這種實現稱為”擦除”
(編譯器使用泛型型別資訊保證型別安全,然後在生成位元組碼之前將其清除)

(8)擦除也造成了上述問題,即不能建立泛型型別的物件,因為編譯器不知道要呼叫什麼建構函式。 如果泛型類需要構造用泛型型別引數來指定型別的物件,那麼建構函式應該傳入類物件,並將它們儲存起來,以便通過反射來建立例項。

型別引數的目標是:

定義泛型

包含有限制類型;

萬用字元的目標是:

使用泛型且我們希望能夠像使用普通型別那樣使用泛型型別:

 為了解決型別不能動態根據例項來確定的缺點,引入了“萬用字元泛型”,

存取原則和PECS法則

總結 ? extends 和 the ? super 萬用字元的特徵,我們可以得出以下結論:

  • 如果你想從一個數據型別裡獲取資料,使用 ? extends 萬用字元
  • 如果你想把物件寫入一個數據結構裡,使用 ? super 萬用字元
  • 如果你既想存,又想取,那就別用萬用字元。

Bloch提醒說,這PECS是指”Producer Extends, Consumer Super”,這個更容易記憶和運用。

“?”代表未知型別

extends關鍵字聲明瞭型別的上界,表示引數化的型別可能是所指定的型別,或者是此型別的子類

super關鍵字聲明瞭型別的下界,表示引數化的型別可能是所指定的型別,或者是此型別的父型別,直至Object

上界add方法受限,下界get方法受限

命名型別引數
推薦的命名約定是使用大寫的單個字母名稱作為型別引數。這與C++ 約定有所不同(參閱附錄 A:與 C++ 模板的比較),並反映了大多數泛型類將具有少量型別引數的假定。對於常見的泛型模式,推薦的名稱是:

* K —— 鍵,比如對映的鍵。
* V —— 值,比如 List 和 Set 的內容,或者 Map 中的值。
* E —— 異常類。
* T —— 泛型。

1.2. 編寫泛型類要注意:

  1) 在定義一個泛型類的時候,在 “<>”之間定義形式型別引數,例如:“class TestGen<K,V>”,其中“K” , “V”不代表值,而是表示型別。

  2) 例項化泛型物件的時候,一定要在類名後面指定型別引數的值(型別),一共要有兩次書寫。例如:

TestGen<String,String> t=new TestGen<String,String>();

  3) 泛型中<K extends Object>,extends並不代表繼承,它是類型範圍限制。

 
  1、泛型的型別引數只能是類型別(包括自定義類),不能是簡單型別。

  2、同一種泛型可以對應多個版本(因為引數型別是不確定的),不同版本的泛型類例項是不相容的。

  3、泛型的型別引數可以有多個。

  4、泛型的引數型別可以使用extends語句,例如<T extends superclass>。習慣上稱為“有界型別”。

  5、泛型的引數型別還可以是萬用字元型別。例如Class<?> classType = Class.forName("java.lang.String");

 泛型方法:

1 根據實參暗示型別引數

2 根據返回值暗示

3 直接傳給型別引數

PS: 編譯器可以暗示型別引數是什麼!即使沒有傳真實型別引數或實參(編譯器根據實參為我們推斷型別引數的值。),但是賦值本身存在暗示。

public class TestExternal {
 
  public static <T> T get(String str){
   return (T)str;
 }
 
 public static void main(String[] args) {
  int str=TestExternal.get("");
 }

}

泛型函式允許型別引數被用來表示方法的一個或多個引數之間的依賴關係,或者引數與其返回值的依賴關係。如果沒有這樣的依賴關係,不應該使用泛型方法。

現在有一個問題:我們應該什麼時候使用泛型方法,又什麼時候使用萬用字元型別呢?

為了理解答案,讓我們先看看Collection庫中的幾個方法。

public interface Collection<E> {

    boolean containsAll(Collection<?> c);

    boolean addAll(Collection<?extends E> c);

}

我們也可以使用泛型方法來代替:

public interface Collection<E> {

        <T> boolean containsAll(Collection<T> c);

        <T extends E>boolean addAll(Collection<T> c);

        //  hey, type variables can have bounds too!

}

但是,在 containsAll 和 addAll中,型別引數T 都只使用一次。返回值的型別既不依賴於型別引數(type parameter)也不依賴於方法的其他引數(這裡,只有簡單的一個引數)。這告訴我們型別引數(type argument)被用作多型(polymorphism),它唯一的效果是允許在不同的呼叫點,可以使用多種實參型別(actual argument)。如果是這種情況,應該使用萬用字元。萬用字元就是被設計用來支援靈活的子類化的,這是我們在這裡要強調的

閱讀Java核心技術泛型設計經驗總結(1)

1 Java 泛型設計中的一些主要限制和使用方面錯誤。

1.1不能將泛型用在建立型別物件中,原因則是因為Java泛型中存在型別擦除的原因,所以會導致在Java虛擬機器執行時,所有泛型型別都會相應的變為它的原始型別,這就意味著如果建立泛型物件則Java虛擬機器會將建立的物件改變為Object這肯定不會是希望所應該有的目的。

1.2不能將泛型應用在基本資料型別中,原因也是因為型別擦除機制的緣故,而基本型別不屬於物件,如果要將基本型別應用到泛型當中則必須使用相應的包裝器來做為替代方式。

1.3不能將泛型用在異常捕獲和用泛型來實現Throwable子類,如果這樣做的會導致無法通過編譯。

1.4泛型陣列是不合法的,原因同樣出於型別擦除會導致建立的陣列變為Object,而如果需要達到這樣的目的,則必須使用型別資訊中的Class類來滿足要求。如果先建立一個型別陣列而後轉型為Object或是先宣告型別陣列而後建立一個Object陣列通過強制型別轉換來實現型別陣列都將導致失敗或者是型別擦除方面的問題。

1.5無法將泛型用於靜態的上下文中,原因同樣還是出於型別擦除,如果允許這樣實現則有可能使用泛型建立一個共享Swing構建或者AWT構建等一些會出現嚴重錯誤的缺陷。

1.6使用泛型時應注意覆蓋equals等方法的問題,原因是會導致編寫出的程式碼導致一些難於發現的錯誤,但是表面上卻很難讓進行錯誤的查詢。

1.7在使用泛型時不能同時為兩個介面的子型別,而這2個介面是同一個介面的不同引數,

這條限制可以理解為,如果Employee類實現了Comparable<Employee>而Manager類繼承了Employee類並且實現Comparable<Manager>介面,這樣一來雖然子類和父類實現介面的約束條件是不同的,但是卻是是想了同樣的介面而該介面的型別引數又是父子的繼承關係。則會與Java虛擬機器合成的橋方法可能產生一些衝突。

Java泛型的約束和限制的總結。經過閱讀了2遍書籍和了解,感覺Java的泛型設計和C#,C++,Python(該語言經過一些瞭解雖然語言本身不顯示包括泛型的概念,但是由於其是純面向物件和一些語言特性,則它隱身的方法中都包含擁有泛型特點)等語言的泛型設計的不同是Java由於其歷史原因包含了型別擦除的特點,所以導致了很多在其它同類語言中不存在的限制和要求,導致有些時候會出現很多難以發現和錯誤提示含糊不清或者難以理解的情況。

(1)泛型限制

泛型限制的語法。
extends:限制泛型型別必須為某個類的後代,包括本型別。
語法:<T extends parentClass>
這裡,T為泛型型別,extends 關鍵字限制泛型型別必須是parentClass的後代。parentClass 指定父類的型別,也可以是介面。
在Java語言中,對類只能單繼承,對介面可以多繼承,如果要限制指定型別必須從某個類繼承,並且實現了多個介面,則語法為:
<T extends parentClass & parentInterface1 & parentInterface2>
注意,類必須在介面前面。 

(2)萬用字元:包括邊界萬用字元(上界和下界萬用字元)。