---
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 泛型`