kyle shanks
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Publish Note

      Everyone on the web can find and read all notes of this public team.
      Once published, notes can be searched and viewed by anyone online.
      See published notes
      Please check the box to agree to the Community Guidelines.
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Publish Note

Everyone on the web can find and read all notes of this public team.
Once published, notes can be searched and viewed by anyone online.
See published notes
Please check the box to agree to the Community Guidelines.
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
9
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
--- title: 'Java 泛型' disqus: kyleAlien --- Java 泛型 === ## Overview of Content 主要有分為 **泛型類、泛型方法、泛型界面** :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**深入了解 Java 泛型:從基本概念到實踐應用 | 3 種泛型 | Java、C++、C# 泛型概念**](https://devtechascendancy.com/java-generics-complete-guide/) ::: [TOC] ## Generic for what 對於相同的運算,或是流程不用重複寫相同的程式,以下程式可看到,明明都是加法的程式,不過就因為「類型(type)」不同就需要做多次相同程式的撰寫… 而使用泛型(Generic)就可以達到相同的目的,只撰寫一次程式(說繞口一點就是「**參數化類型**」) ```java= public int sumOfTwoInt(int a, int b) { return a + b; } public float sumOfTwoFloat(float a, float b) { return a + b; } ``` ### 泛型優點 * 如果有定義泛型就不用強制轉換,它會更加有效的使用「靜態程式的檢查特性」!讓產出的程式更加穩定,提早在「編譯期」就發現問題 > ![image](https://hackmd.io/_uploads/SypAQ69DR.png) * **對於不同類型(`Type`)的數據,也可以用相同的程式處理**,達到很好的兼容效果 **泛型如果未定義類型,是可以任意添加其他類型的值,Java 並不會檢查**,範例如下 ```java= // 未定義泛型時 List list = new ArrayList(); list.add("Hello"); list.add("World"); list.add(123); ``` > ![](https://i.imgur.com/ODmloWE.png) > > 將一個對象加入集合中,如果**未定義類型**就不會檢查其類型,取出時預設為 Object 類型 > 此時強制轉型會拋出異常,**ClassCastExecption** 強轉型錯誤 > ### 泛型的來源、原理 * JDK 5 後 Java 才加入泛型,往往有人稱 Java 的泛型為「偽泛型」,這其實是 為了向下兼容;另外在 JVM 虛擬機是不支持泛型的,所以 Java 語言為了實現泛型使用了 **==偽泛型==** :::success Java 會在 編譯期間擦除 所有的泛型訊息,這樣就可以讓 JVM 使用相同的字節碼而不必新增,在 JVM Runtime 時就不存在所謂的泛型訊息 (後面會介紹泛型擦除) ::: * 由於泛型擦除的存在,Java 的泛型是在編譯期間處理的,而不是在運行時… 這意味著在 JVM 運行時,不存在泛型類型的信息,所有的類型檢查和轉換都在編譯期間完成。這樣做的好處是可以簡化 JVM 的設計,並且確保向下兼容 :::info * **運行時類型轉換** 編譯器會在必要的地方插入類型轉換,以確保類型安全。這些轉換是在「運行時, Runtime」執行的,但它們是基於編譯期間插入的轉換操作 ::: ### Java 泛型副作用 * ++**不能使用基本數據類型**++ (byte、char、short、int、long、double),因為編譯過後會經過擦除,擦除後後會轉換為 Object 類型,在轉換回特定類型時無法得知「目標類的類資訊」 * **++不能++使用 `instanceof` 運算符號**,同樣是因為類型擦除後所帶來的副作用,會導致無法判別類型(`Type`) > 擦拭後都是 Object 類型,沒得判斷 > > ![image](https://hackmd.io/_uploads/H1sNPaqw0.png) * 「泛型類」不能使用在靜態:因為泛型創建物件時才能確定,但靜態方法、靜態參數,是不用加載就可以使用 (泛型方法則可以,因為泛型方法是呼叫後才加載) > ![image](https://hackmd.io/_uploads/By-IY6cPC.png) * **泛型可能會導致多載方法衝突**,同樣是因為擦除後會轉為 Object 類,導致++重複定義++ ```java= // 泛型 public void HelloWorld(T t) { // 編譯字節碼後,同 Object t ... } public void HelloWorld(Object t) { ... } ``` * **無法創建泛型對象,但==可以使用反射創建泛型對象 (因為反射就是 Runtime 運行)==** ```java= public static <E> void yo1(E e) { E elements = new E(); // Error } public static <E> void yo2(E e, Class<E> clz) { E elements = clz.newInstance(); } ``` :::warning * **Java 中沒有所謂的泛型數組**,同樣也是因為擦除,無法判斷是否是同一類型 ```java= // 非法:不能創建泛型數組 List<String>[] stringLists = new List<String>[10]; ``` > ![image](https://hackmd.io/_uploads/rJIBqT9wA.png) ::: ## 泛型類、界面、方法 * **泛型的本質是為了==參數化類型==**,也就是指定參數類型,把類型當成引數一樣傳遞 * 在泛型的使用中,操作數據類型被指定為一參數,這參數可被用在 `Class`、`interface` and `method` * **泛型允許多個變量(多個參數類型)**<T, K, V>,引入一個類型變數 T (其他字母也可以 T, E, V, K 等等) 並用 `<>` 包住 ### Generic Class:泛型類 * 泛型類是以類(`Class`)為基礎再加上泛型類型的符號就可以使用,之後這個類就可以使用泛型指定的類型 ```java= public class Clz<T> { private T data; public Clz(T data) { this.data = data; } } ``` 以下範例 (類型變數用 T 代表),T 為數據類型 ```java= public class GenericClass1<T> { private T data; private GenericClass1() { } public GenericClass1(T data) { this(); // 呼叫無參構造函數 this.data = data; } public T GetData() { return data; } } // 兩個泛型變量 T K... public class GenericClass2<T, K> { private T t; private K k; public GenericClass2() { } public void setDataT(T t, K k) { this.t = t; this.k = k; } public T getTData() { return t; } public K getKData() { return k; } } ``` **---實作---** > ![](https://i.imgur.com/6GZs1Wf.png) ### Generic Interface:泛型界面 * 泛型界面基本上跟 Class 相同,不過泛型界面是使用界面(`interface`)作為基礎,再加上泛型類型的符號,之後就可以使用泛型界面 ```java= public interface GInterface<T> { <T> getData(); } ``` 以下範例使用,兩種實現方式來使用泛型界面,分別是 ^1.^ 實作界面時未指定類型(將指定類型的責任往使用該類者轉移),^2.^ 實作界面後指定類型(泛型類同樣也可以這樣使用) ```java= // 泛型界面 public interface GenericInterface<T> { public void setData(T t); public T getData(); } // 實現 1 未指定類型 public class NormalClass<T> implements GenericInterface<T> { public T data; public void setData(T t) { this.data = t; } public T getData() { return data; } } // 實現 2 指定類型 public class NormalClass2 implements GenericInterface<String> { public String data; public void setData(String t) { this.data = t; } public String getData() { return data; } } public static void main(String[] args) { NormalClass<String> n = new NormalClass<>(); n.setData("Hello World"); n.getData(); NormalClass2 n2 = new NormalClass2(); n2.setData("Hello World"); n2.getData(); } ``` **---實作---** > ![](https://i.imgur.com/me4DXjA.png) * **泛型類(`Generic Class`)的繼承規則**:泛型類可以繼承或是拓展其他泛型類;像是以下範例,`NormalClass` 實作泛型類 `MyClz` ```java= abstract class MyClz<K> { public abstract int compareTo(K k); } class NormalClass<T, K> extends MyClz<K> { private T data; public NormalClass(T data) { this.data = data; } @Override public int compareTo(K k) { return 0; } } ``` ### Generic Method:泛型方法 * Generic Method 可使用在任何場景的類中… 包括泛型類、普通類(也就是不單指定泛型類) ```java= // 泛型類型 class GenericClz<T> { // 泛型方法 void setData(T data) { // TODO: } } // 普通類 class NormalClz { // 泛型方法 <T> void setData() { } } ``` * 「**混和型**」也可以使用 Generic Method,可載入不同於「類」定義時的類型… 範例如下: ```java= public static void main(String[] args) { NormalClass n1 = new NormalClass(); String s1 = n1.<String>GetGeneric("Hello", "World", "123"); System.out.println(s1); int i1 = n1.<Integer>GetGeneric(111, 222, 333); System.out.println(i1); // 原本定義為 String 型態的變數 T (內部就是 String 型態) NormalClass2<String> n2 = new NormalClass2<>(); // 泛型方法可分開定義,自己定義方法內部使用的 T 為 Double 類型 double i2 = n2.<Double>GetGeneric(0.0, 0.1, 0.2, 0.3, 0.4, 0.5); System.out.println(i2); } // 1. 一般類別使用 Generic Method public class NormalClass { // <T> T,<T> 為返回的型態 T 為返回的值 public <T> T GetGeneric(T ...a) { return a[a.length / 2]; } } // 2. 混和 Generic Class interface and method public class NormalClass2<T> implements GenericInterface<T> { public T data; @Override public void setData(T t) { this.data = t; } public <T> T GetGeneric(T ...a) { // 內部的類型 T != 外部的類型 T return a[a.length / 2]; } } ``` **---實作---** > ![](https://i.imgur.com/3tIDKcC.png) ## 限定類型 Qulified Type 有時會對類型變量進型約束,例如約束型態 T 必須要實現 Comparable 界面 `<T extends Comparable> ` > 如果不進行約束將無法確保該型態是否可執行此界面的函數 ```java= // 未用現定符號,「無法」確定是否都有實現 compareTo 方法,這時就需要限定類型 Qulified Type public static <T> T min(T a, T b) { return (a.compareTo(b) > 0) ? a : b; } ``` ### Constraint (約束)、limitation (限制) * 基礎限定方法 `<T exetnds A>`,其中 extends 左右都允許多個泛型符號,像是… `<T, k extends A & B>` :::danger **注意** 限定類型中 ==只能有一類 (一個繼承),而且必須是第一個== ! ::: * 可限定多個類型,使用 `&` 連接多個限制條件 `<T extends A & b>`,當然如上面所說,第一個 A 是類別,之後的 b 則是界面 ```java= // 1. 使用限定符號 public static <T extends Comparable> T minNumber(T a, T b) { return (a.compareTo(b) > 0) ? a : b; } // 2. 多個限定 public static <T extends Comparable & Serializable> T maxNumber(T a, T b) { return (a.compareTo(b) > 0) ? b : a; } // 3. 類限定類 Activity為類型,Serializable為界面 public static <T extends Activity & Serializable> T recordActivity(T a) { Log.d("TEG", a.toString); return a; } ``` **---實作---** > ![](https://i.imgur.com/TKP9JxC.png) * 想看更多的範例,可參考 Java 源碼中實現的 `List` and `ArrayList` 類,這些類型都有使用泛型約束... > ![](https://i.imgur.com/W1EPGdw.png) ## Universal Symbol 通配符 Java 的泛型有另外一個概念「通配符號, Universal Symbol」,通配符會使用「問號 `?`」來呈現 **它有兩種拓展使用方式:`extends`、`super` ,它們個代表了不同的限制,而在了解通配符之前要先了解,為何要使用它 ? 這就要先了解 繼承 & 泛型的關係** ### 泛型類繼承 * **泛型類繼承** 1. 泛型雖然在編譯過後在字節碼(`Byte Code`)被擦除,但是仍會在檢查的時候用到,只要 **泛型類型的原型基類相同,就符合泛型類繼承** 2. 如果與基類不同就無法實現泛型類繼承… 簡單範例如下 ```java= public class Extends2 { public static void main(String[] args) { //"1. " OK Clothes<UNIQLO> c1 = new T_shirt<>(); Clothes<UNIQLO> c2 = new Colorful_T_shirt<String, UNIQLO>(); Clothes<UNIQLO> c3 = new LongPants<Integer, UNIQLO>(); //"2. " ERROR, 基類為 Clothes<UNIQLO>,與 Clothes<String> 不同 Clothes<String> c3 = new Colorful_T_shirt<String, UNIQLO>(); } } class UNIQLO { } interface Clothes<T> { } class T_shirt<T> implements Clothes<T> { } class Colorful_T_shirt<E, T> extends T_shirt<T> { } class Pants<T> implements Clothes<T> { } class LongPants<K, T> extends Pants<T> { } class ShortPants<K, T> extends Pants<T> { } ``` **--實作--** > ![image](https://hackmd.io/_uploads/BylEAJ_dC.png) ### 通配符功能:讓泛型類之間產生關係 * 要了解通配符能為泛型帶來什麼樣的功能,先要了解 **泛型類之間的關係**,在以下範例中我們來觀察一下,泛型是否可以判斷「繼承」的特性? ```java= public class Extends { public static void main(String[] args) { // 泛型類未指定類型 Shop s1 = new Shop(); Shop s2 = new Shop(); //"1. " s2 = s1; // ok, but not good Shop<Food> foodShop = new Shop<>(); Shop<Fruit> fruitShop = new Shop<>(); Shop<Meat> meatShop = new Shop<>(); //"2. " foodShop = fruitShop; // Err foodShop = meatShop; // Err //"3. " foodShop = (Shop<Food>)meatShop; // Err } } class Shop<T> { private T t; void set(T t) { this.t = t; } T get() { return t; } } class Food { } class Meat extends Food { } class Beef extends Meat { } class Fruit extends Food { } class Banana extends Fruit { } ``` 1. 在一般類時可賦予值,所以 ok(但是不夠好,無法善用靜態語言的檢查特性) 2. 無法使用的原因是它規定了傳入的參數必須是 `Shop<Fruit>`,**雖然一般的繼承有關係,但是++到了==泛型類別時它無法自動判斷是否有關係==++**,它們全都繼承於 Object 類,導致編譯器無法判斷兩個類之間的關係 (因為 ++Shop 才是主體,泛型會被擦除++,所以繼承於 Object) > ![](https://i.imgur.com/oECAnVC.png) 3. 同樣的,也 **無法強制轉型**,Java 不允許這樣的操作,會提醒我們 Inconvertible types; cannot cast 錯誤 :::info * 從這個例子中,可以引出 **要 ==讓普通繼承 & 泛型++產生關係++就要使用通配符==** ::: ### 使用通配符 * 若泛型使用了 通配符 `?`(單單只有通配符),那就不能設定也不能取值,**==目的是為了 ++類型檢查++==**… 範例如下 ```java= public class Extends { public static void main(String[] args) { test(); } private static void test() { Shop<?> shop = new Shop<>(); // 無法確定確切的類 //shop.set(new Banana()); //shop.set(new Fruit()); //shop.set(new Food()); shop.set(null); // null 可以 // 無法確定取出的類 //Food d = shop.get(); //Fruit f = shop.get(); //Banana b1 = shop.get(); Banana b2 = (Banana) shop.get();// 強制轉型,可能有問題 warning Object o = shop.get(); // 所有類的父類 Object 一定是 } } class Shop<T> { private T t; void set(T t) { this.t = t; } T get() { return t; } } class Food { } class Meat extends Food { } class Beef extends Meat { } class Fruit extends Food { } class Banana extends Fruit { } ``` **--實作--** > ![](https://i.imgur.com/zm0Ul9d.png) * 若泛型使用了 通配符 `?`(單單只有通配符),但是作為「引數」,代表 全部類型都可以接受 ### 通配符 & extends * **通配符與 `extends` 配合後的功能是:==安全的 ++取得 (get)++ 數據==**;**作為引數**,基礎使用 `<? extends X>`,表示類型的 **++上限到 X 類++,包含 X 類以及其衍生子類** ```java= public static void main(String[] args) { funcPrint_2(shop1); // ok "1: " funcPrint_2(shop2); // ok //Shop<T> 是泛型 Shop<? extends Fruit> shop3 = new Shop<>(); "2: " Fruit f1 = shop3.getType(); "3: " Apple a1 = (Apple) shop3.getType(); Apple apple = new Apple(); "4: " shop3.setType(); // Fail !! } static void funcPrint_1(Shop<Fruit> p) { System.out.println("Func1: " + p.getType().type()); } static void funcPrint_2(Shop<? extends Fruit> p) { System.out.println("Func2: " + p.getType().type()); // 安全取得 p.set(new Fruit()); // Fail !! } ``` 1. 使用 `<? extends Fruit>` 拓展了傳入泛型類別的寬度,拓展範圍是 Fruit 跟它的子類 (include Fruit) 2. 可**安全的取得類,因為返回的一定是 Fruit 類** (上限到 Fruit) 3. **返回的是 Fruit 類,它的 ++子類要強制轉型++** 4. 只知道設定的是 apple,但卻**不能具體的知道 Shop 內的類型變數 T 要設定哪個類** (可能是 Fruit、Apple、HonFuShi、Orang) :::success **主要用來安全的訪問(取得)數據**,就像是 Kotlin 的協變 (out) ::: > ![](https://i.imgur.com/NbHzyYS.png) * 範例二:**強調通配符 extends 是限定泛型**,extends 可安全的取出 ```java= public class Extends { public static void main(String[] args) { testExtends(); } private static void testExtends() { Shop<? extends Fruit> shop = new Shop<>(); // extends 用於安全取值 //shop.set(new Banana()); // 無法確定確切的類,有可能是 Fruit、Banana or ... //shop.set(new Fruit()); //shop.set(new Food()); shop.set(null); // null 可以 Food d = shop.get(); Fruit f = shop.get(); // 只能肯定一定是 Fruit //Banana b1 = shop.get(); // 只能取出是 Fruit 的基類 Banana b2 = (Banana) shop.get(); Object o = shop.get(); } } class Shop<T> { private T t; void set(T t) { this.t = t; } T get() { return t; } } class Food { } class Meat extends Food { } class Beef extends Meat { } class Fruit extends Food { } class Banana extends Fruit { } ``` **--實作--** > ![](https://i.imgur.com/kVajozS.png) ### 通配符 & super * **通配符與 `super` 配合的功能是:==安全的 ++設定++ 數據==**;**作為引數**,基礎使用 `<? super X>`,表示類型的 **++下限是 X++ 類,包含 X 類 以及其父類(超類)** :::warning **限定符不能使用 super**,像是如果使用 **`<T super Shop>` 語法錯誤** ::: ```java= public static void main(String[] args) { Shop<Food> shop1_1 = new Shop<>(); Shop<Fruit> shop2_1 = new Shop<>(); Shop<Apple> shop3_1 = new Shop<>(); Shop<Orange> shop3_2 = new Shop<>(); Shop<HonFuShi> shop4_1 = new Shop<>(); funcSuperPrint(shop1_1); // ok (基類 funcSuperPrint(shop2_1); // ok (包含限定界線 "1: " funcSuperPrint(shop3_1); // fail funcSuperPrint(shop3_2); // fail funcSuperPrint(shop4_1); // fail Shop<? super Fruit> test = new Shop<>(); "2: " test.setType(new Food()); // fail test.setType(new Fruit()); // ok test.setType(new Apple()); // ok "3: " Object o = test.getType(); } public static void funcSuperPrint(Shop<? super Fruit> p) { System.out.print("Super Func: " + p.getType()); // fail 返回的是 Object 類 "4: "System.out.println("Super Func: " + p.getType().type()); } ``` 1. 使用 `<? super Fruit>` 拓展了傳入泛型類別的寬度,拓展範圍是 `Fruit` 跟它的父類(include Fruit),所以不符合入參拓展規定 2. 編譯器不知道set 它的具體類型,但可**保證 `Fruit` and 其 `子類` 可安全轉型成 Fruit,set 無法得知設定的超類是否能轉型成 Fruit** 3. **返回的類別是 Object,無法得知回傳的具體類別**(有可能是 Food、Fruit),**但一定是 Object 的子類** 4. 返回 Object 類別,除非強轉否則無法使用指定的子類 Function :::success **主要用來安全的設定 (set) 數據**,只可寫入 X 的子類,就像是 Kotlin 的逆變 (in) ::: > ![](https://i.imgur.com/uxsX8fo.png) * 範例二:集合的安全設定 (set) 就可以使用到 `通配符 + super` ```java= public class Extends { public static void main(String[] args) { } private static void testSuper() { // 安全設定 Fruit 基類數據 Shop<? super Fruit> shop = new Shop<>(); // 能確定一定是 Fruit 的衍生子類,要嘛 Fruit 要嘛 Fruit 的子類 shop.set(new Banana()); shop.set(new Fruit()); //shop.set(new Food()); // Err 無法確定跟 Fruit 的關係 shop.set(null); // null 可以 // 無法確定取出的類 //Food d = shop.get(); //Fruit f = shop.get(); //Banana b1 = shop.get(); Banana b2 = (Banana) shop.get(); // 只能確定取出的一定是 Fruit 的基類,而 Object 則一定 Ok Object o = shop.get(); } } class Shop<T> { private T t; void set(T t) { this.t = t; } T get() { return t; } } class Food { } class Meat extends Food { } class Beef extends Meat { } class Fruit extends Food { } class Banana extends Fruit { } ``` :::info * 為何安全設定數據,但無法安全取得數據 ? **Get 時無法確定是哪一個類,主要原因是因為泛型限制丟失**,只能用 Object 存放 ::: > ![](https://i.imgur.com/TUq9QHB.png) ### super & extends 關係 & 使用 * `Shop<?>` 非限定通配符,等同於 Shop<\? extends Object> * `Shop<? extends T>`(上界)、`Shop<? super T>`(下屆) 兩者為 **限定通配符** ```java= class Shop<T> { private T t; void set(T t) { this.t = t; } T get() { return t; } } class Food { } class Meat extends Food { } class Beef extends Meat { } class Fruit extends Food { } class Banana extends Fruit { } ``` 配合上面的程式看下面限定 & 非限定通配符的關係圖 > ![](https://i.imgur.com/oNJH5it.png) ### Java Collections 通配符使用 * Java Collections 內大量地使用了泛型 `super` & `extends`,以 copy 為例 **使用了 ++super 安全的設定 T 的衍生類數據++**,**使用 ++extends 安全的取得了 T 的衍生類數據++**,以此做為 copy 基礎 ```java= public static <T> void copy(List<? super T> dest, // 安全 Set List<? extends T> src) { // 安全 Get int srcSize = src.size(); if (srcSize > dest.size()) throw new IndexOutOfBoundsException("Source does not fit in dest"); if (srcSize < COPY_THRESHOLD || ... } else { // 取得 Iterator ListIterator<? super T> di=dest.listIterator(); ListIterator<? extends T> si=src.listIterator(); for (int i=0; i<srcSize; i++) { di.next(); di.set( si.next() // 安全取得 ); // 1. 安全設定 } } } ``` * 而為何要這樣使用 `extends` & `super`,可以看下面的例子 1. 類關係 ```java= class PC { int id; PC(int id){ this.id = id; } } class CPU extends PC { int id; CPU(int id){ super(id); this.id = id; } } ``` 2. Collections 的 copy 函數使用 ```java= // 指定 public static void copy_1(List<CPU> p1, List<CPU> p2) { Collections.copy(p1, p2); } // 限定類型 public static <T> void copy_2(List<T> p1, List<T> p2) { Collections.copy(p1, p2); } // 限定 super (set) public static <T> void copy_3(List<? super T> p1, List<T> p2) { Collections.copy(p1, p2); } // 限定 super (set)、extends (get) public static <T> void copy_4(List<? super T> p1, List<? extends T> p2) { Collections.copy(p1, p2); } ``` 3. 測試 ```java= public class UseMixSuperExtend { public static void main(String[] args) { fixed(); System.out.println(""); gMethod(); System.out.println(""); Mix_1(); System.out.println(""); Mix_2(); } // 固定 (指定) 泛型 public static void fixed() { List<CPU> src = new ArrayList<>(); src.add(new CPU(1)); List<CPU> dest = new ArrayList<>(); dest.add(new CPU(2)); System.out.println("cpu-cpu before copy: " + dest.get(0).id); copy_1(dest, src); // src 複製到 dest System.out.println("cpu-cpu after copy: " + dest.get(0).id); } // 泛型方法 public static void gMethod() { // 使用基類 PC List<PC> src = new ArrayList<>(); src.add(new PC(1)); // 使用基類 PC List<PC> dest = new ArrayList<>(); dest.add(new PC(2)); System.out.println("PC-PC before copy: " + dest.get(0).id); UseMixSuperExtend.<PC>copy_2(dest, src); // 轉為 PC 泛型 System.out.println("PC-PC after copy: " + dest.get(0).id); } public static void Mix_1() { // 指定衍生類 CPU List<CPU> src = new ArrayList<>(); src.add(new CPU(1)); // 使用基類 PC List<PC> dest = new ArrayList<>(); dest.add(new PC(2)); System.out.println("cpu-PC<CPU> before copy: " + dest.get(0).id); // // 使用泛型方法 Err,因為第一個參數不符合 // UseMixSuperExtend.<PC>copy_2(dest, src); // 使用通配符轉型為 CPU UseMixSuperExtend.<CPU>copy_3(dest, // <? super CPU> src); System.out.println("cpu-PC<CPU> after copy: " + dest.get(0).id); } public static void Mix_2() { // 指定衍生類 CPU List<CPU> src = new ArrayList<>(); src.add(new CPU(1)); // 使用基類 PC List<PC> dest = new ArrayList<>(); dest.add(new CPU(2)); System.out.println("cpu-PC<PC> before copy: " + dest.get(0).id); // 使用通配符轉型為 PC Err,因為第二個參數是 CPU //UseMixSuperExtend.<PC>copy_3(dest, src); UseMixSuperExtend.<PC>copy_4(dest, // <? super PC> src); // <? extends PC> System.out.println("cpu-PC<PC> after copy: " + dest.get(0).id); } } ``` `Mix_1` & `Mix_2` 的差別在於,使用不同的泛型方法類型,但 `Mix_2` 更加完善 > ![](https://i.imgur.com/o49uye5.png) ### 通配符 vs. 限定類型泛型 * 限定類型的泛型 **`T` 必須是具體的某一種類**,會有類型之間的轉換問題 * **通配符 `?` 讓泛型轉型更靈活**,**==反射可以打破通配符的限制==** > **類似於限定類別 `<T extends X>`,但限定類別用於限定泛型的類型** > > **通配符 `<? extends X>` 用於,限定/拓展 Func 接收的參數,並給予它限制** | 限定符類型 | 範例 | | -------- | -------- | | 非限定通配符 | `Plate<?>` or `Plate<? extends Object>` | | 限定通配符(指定類型) | `Plate<? extends Fruit>` or `Plate<? super Fruit>` | ## 虛擬機是如何實現泛型 最早是出現在 C++ 的模板(Temple),Java 只能通過 Object 是所以類的父類 and 強轉兩個特點型成泛型,Object 可轉型成任何類,只有在運行期才會知道是什麼類,編譯期無法檢察 Object 是否轉型成功,因此有許多的 `ClassCastException` 風險會轉駕到執行期 ### C++ 模板 * C++ 內部有真正的泛型 > 最早是出現在 C++ 的 [**模板(Temple)**](https://hackmd.io/KIxpdZ8lRKisv8N3UhvklQ#C-%E6%A8%A1%E6%9D%BF) ```cpp= template<typename t> class TemplateStack { private: enum {MAX = 10}; int index; T List[MAX]; public: TemplateStack() : index(0) {} bool isEmpty() const; bool isFull() const; bool push(T t); bool pop(T& t); virtual ~TemplateStack() {}; }; ``` ### C# 泛型 - 類型膨脹 * C# 裡面的泛型無論在程式源碼中,編譯後的程式 IL(`Intermediate Language` 中間語言,這時候泛型是一個佔位符),或 Running time CLR (`Common Language Runtime` 通用運行語言[CLR](https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E8%AA%9E%E8%A8%80%E9%81%8B%E8%A1%8C%E5%BA%AB)) **List<int\>、 List<String\> 是兩種不同類型,都有自己的虛擬方法表**,這種實現稱為`類型膨脹`,基於這種方法型成的泛型為 **真實泛型** ### Java - 偽泛型 * **Java / kotlin 虛擬機其實內部並無泛型**,並不是真正的泛型 ,JDK 5 才有泛型,==為了向上兼容,才會演變成 ++偽泛型++== > 在編譯後的字節碼中以替換成原生類型(Raw Type),並在相應的第方插入強制轉型代碼,所以對於運行期的 **Java 語言來說 `List<Integer>`、`List<String>` 就是同一類別** > > Java 語言中的泛型實現方法稱為 ==**類型擦除**==,基於這種方法實現的泛型是**偽泛型** * **在泛型中並不是所有的擦除都會變成 Object 的,當泛型有限制性,Test<T extends Apple\>,則會被擦除成 Apple** ```java= List list = new ArrayList<Integer>(); List<String> list2 = list; ``` > 擦除過後 ArrayList<Integer\> 轉為 Array List,List<String\> 轉為 List ## Java 泛型擦除 * 在編譯成字節碼後,**泛型會擦除**,如果**沒有限定會變成 Object,如果有限定則會擦除成限定類** ```java= /** * 源碼 `.java` 檔 */ public interface Plate<T> { void set(T t); T get(); } /** * 擦除後 `.class` 檔 */ public interface Plate { void set(Object t); // 轉換為 Object Object get(); } ``` * 在擦除完後,當有需要時就會新增一個 **「橋接函數」,調用時用橋接函數,並且++強制轉型++再呼叫轉型的方法** > 生成橋方法的目的也是為了保持多態性 ```java= public class MyPlate implements Plate<T extends Comparable> { public MyPlate() { } public void set(Comparable t) { } public Comparable get() { return ....; } // 橋接函數,強制轉型 @Override public void set(Object t) { set((Compareable) t); } // 橋接函數,強制轉型 @Override public Comparable get(Object t) { return (Comparable)get(); } } ``` ### 泛型擦除的殘留:反編譯 * 使用工具 `jd-gui` 打開,字節碼還是會殘留,所以打開時仍然是有泛型的 T,**保留該泛型格式是對字節碼分析有好處的** * 而 **保留訊息則是保留在 ==類的常量池中==,這也就是為何明明被擦除成 Object 卻仍然可以反射的原因**! ```java= // .java 檔案 class FruitPlate<T> implements Plate<T> { private LinkedList<T> list = new LinkedList<>(); @Override public void put(T t) { list.add(t); } @Override public T get() { return list.peek(); } } // javac 編譯過後的 class 檔案使用 jd-gui 開啟 package testMyJava.Generic; import java.util.LinkedList; class FruitPlate<T> implements Plate<T> { private LinkedList<T> list = new LinkedList<>(); public void put(T paramT) { this.list.add(paramT); } public T get() { return this.list.peek(); } } ``` **--實作--** > ![](https://i.imgur.com/Yy6Rce1.png) ## 泛型使用注意 ### Heap Pollution * `Heap Pollution` 是將一個 **未定義參數型態的 Collection 物件指定給一個有定義參數型態的 Collection 物件** ```java= public class TestAnnotation { public static void main(String[] args) { List list = Arrays.asList(args); //"1. " ToDo.print(list); } } class ToDo { static void print (List<Integer> list) { for(int i : list) { System.out.println("value: " + i); } } } ``` 1. 將一個==無泛型參數指向一個有泛型參數== > 這時編譯器會出現警告,因為傳入一個未定一類型的 Collection 類 > > 雖然可編譯成功,但是很明顯傳入的類型應該是 String,執行時就會出現錯誤,**ClassCastException** * 以下就會出現 ClassCastException,因為類型的不同,**編譯檢查不支持泛型** ```java= class TestHeapPolluation { public void Test() { // 預設為 Object List list = new ArrayList<Integer>(); list.add(3); List<String> list2 = list; String str = list2.get(0); // Err ! } } ``` ## 重點回顧 * **Array 中不可以使用泛型** > 實現就不支持 * ArrayList<String\> a = new ArrayList<Object\> ? > 雖然 String & Object 是繼承關係,但是 **泛型中是沒有關係的** * Set & Set<\?> 區別 ? > Set 不受檢查機制檢查,Set<\?> 仍要接受類型檢查 * List<\?> & List<Object\> 的差別? > 使用通配符號,傳入的引數沒有限制,Object 有限制 * 泛型是啥? 泛型好處? > **參數化類型機制** > 1. 代碼復用 > 2. 類型檢查推到編譯期間 * 泛型如何工作的? 類型擦除 > Java 中是偽泛型,編譯期間會去檢查泛型類型 > Java 虛擬機不支持泛型,所以在轉變為 二進制時就會擦除類型 * 限定通配符號 > <\? extends String> * PECS > PE : Product Extends <\T extends Object> > CS : Consumer Super <\T super Object> * 無法運行時檢查 > if(a instances Object) // Err ## Appendix & FAQ :::info <!-- Where is different between the universal symbol and Qulified Type? --> ::: ###### tags: `Java 基礎進階` `Java 泛型`

Import from clipboard

Paste your webpage below. It will be converted to Markdown.

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template is not available.
Upgrade
All
  • All
  • Team
No template found.

Create custom template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

How to use Slide mode

API Docs

Edit in VSCode

Install browser extension

Get in Touch

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

No updates to save
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully