---
# System prepended metadata

title: '[Java] 型別擦除 Type erasure'
tags: [Spring, Spring Boot, Java, 型別擦除]

---

# [Java] 型別擦除 Type erasure

型別擦除是 Java 泛型(Generics)設計時的一種核心機制。
Java 為了箱溶於舊版(1.4之前)的程式，在編譯後會把泛型的型別資訊擦除，只保留原本的類型，真正運行石臼不再有泛型資訊存在。

## 先來舉例

```java
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
```

我們都知道 Java 的運行機制是基於類別(Class)的，這兩行程式碼在編譯後會被轉換成：

```java
List list1 = new ArrayList();
List list2 = new ArrayList();
```

也就是說 `List<String>` 和 `List<Integer>` 在編譯後都變成了 `List`，這就是型別擦除的結果。
實際上在 JVM 中運行時，這兩個 `List` 物件並沒有任何型別資訊，它們都是 `List` 類型。

## 為什麼要這樣設計?

- 向下相容性：Java 泛型是在 Java 5 引入的，為了讓舊版程式碼能夠與新版本兼容，必須在編譯時擦除型別資訊。
- 型別檢查發生在"編譯期"，運行期不會有型別安全的 overhead。

## 型別擦除對我有什麼影響?

1. 不能直接判斷泛型參數的型別
    ```java
    List<String> list1 = new ArrayList<>();
    if (list1 instanceof List<String>) { // 編譯錯誤
         // ...
    }
    ```
    而應該要寫成
    ```java
    List<String> list1 = new ArrayList<>();
    if (list1 instanceof List) {
         // ...
    }
2. 無法建立泛型陣列
    ```java
    List<String>[] arr = new ArrayList<String>[10]; // 編譯錯誤
    ```
3. 強制轉型警告
    因為型別資訊會被擦除，Java 在某些情況下會強制你轉型，但無法保證型別安全

## @SuppressWarnings("unchecked")

由於型別擦除，有些地方需要「強制轉型」（unchecked cast），但編譯器會發出「Unchecked cast」警告。
這時可以加上 @SuppressWarnings("unchecked") 來壓制這類警告。

```java
@SuppressWarnings("unchecked")
public <T> List<T> getList() {
    return (List<T>) new ArrayList<>();
}
```

這裡因為無法確定 `List<T>` 的實際型別，所以需要使用 `@SuppressWarnings("unchecked")` 來告訴編譯器這是有意為之的。

## 實務應用

1. 泛型 Repository
    假設我們用 Spring data JPA，常會寫一個抽象的 Repository 類別，裡面使用泛型來處理不同的實體類別。
    ```java
    public interface BaseRepository<T, ID> extends JpaRepository<T, ID> {}
    ```
    若我們想要寫一個自訂共用方法
    ```java
    public class MyRepositoryImpl<T, ID> implements MyRepository<T, ID> {

        @Override
        @SuppressWarnings("unchecked")
        public List<T> findByRawSql(String sql) {
            // 假設 jdbcTemplate 查詢返回 List<Map<String, Object>>
            List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql);

            List<T> result = new ArrayList<>();
            for (Map<String, Object> row : rows) {
                // 這裡通常會用反射或手動轉型
                result.add((T) mapToEntity(row)); // 這裡就是 unchecked cast
            }
            return result;
        }
    }
    ```
    這邊我們就會使用 `@SuppressWarnings("unchecked")` 來壓制編譯器的警告。
    因為 mapToEntity(row) 回傳的是 Object，你只能強轉型成 T，但 Java 編譯器無法驗證這個轉型是否正確，所以會出現 unchecked cast 警告。
    這就是型別擦除的影響：無法在運行時檢查泛型型別，只能信任程式設計師。

2. EventListener 泛型應用
    Spring 事件機制也常遇到泛型擦除問題
    ```java
    @EventListener
    public <T> void handleMyEvent(MyEvent<T> event) {
        T data = event.getData(); // 無法判斷 T 的具體型別
    }
    ```
    假如你要把這個 event 轉型給另一個 service 處理：
    ```java
    @SuppressWarnings("unchecked")
    public void publishEvent(Object obj) {
        MyEvent<String> event = (MyEvent<String>) obj;
        applicationContext.publishEvent(event);
    }
    ```