最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot泛型封裝的坑

 更新時間:2026年07月22日 09:10:22   作者:神奇小湯圓  
本文總結了泛型封裝中8個高頻問題,包括泛型擦除、instanceof失效、new T()報錯、序列化變成LinkedHashMap等,并提供可直接落地的解決方案,幫你徹底搞定泛型難點

前言

CRUD 寫多了才發(fā)現(xiàn):泛型用對了是神器,用錯了是噩夢。在

寫業(yè)務代碼時,泛型是我們每天都在用的東西:

Result<List<UserDTO>> getUsers();
BaseService<User, UserDTO> service;
Response<R<List<Order>>> orders();

看起來很標準,但實際項目中,泛型相關的坑一踩一個準:

  • 明明定義了泛型上限,序列化時卻變成了 LinkedHashMap
  • 泛型擦除導致 instanceof 判斷失效
  • 泛型方法里 new T() 報編譯錯誤
  • 工具類封裝時泛型參數(shù)對不上
  • ……

今天盤一盤泛型封裝中 8 個高頻踩坑點,看完直接落地。

1. 坑一:泛型擦除——instanceof判斷永遠為 false

常見寫法

public class Response<T> {
    private T data;
    
    public boolean isList() {
        // 以為是 List 就返回 true?
        return data instanceof List; // 永遠 false!
    }
}

問題在哪

運行期泛型會被擦除為 Object,List<T> 會被擦除成 List,根本不存在 List<String> 這種具體類型。

所以 data instanceof List<String> 語法上就是錯的,編譯器直接報錯。

正確做法

方案一:通過傳入 Class 參數(shù)判斷

public class Response<T> {
    private T data;
    private Class<T> clazz;
    
    public Response(Class<T> clazz) {
        this.clazz = clazz;
    }
    
    public boolean isList() {
        return List.class.isAssignableFrom(clazz);
    }
}
?
// 使用
Response<List<UserDTO>> response = new Response<>(new TypeToken<List<UserDTO>>(){}.getType());

方案二:用 TypeReference 保留泛型信息(JSON 序列化場景)

public class Result<T> {
    private T data;
    
    // 配合 Jackson 使用
    public static <T> Result<T> fromJson(String json, TypeReference<Result<T>> typeRef) {
        try {
            return new ObjectMapper().readValue(json, typeRef);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
?
// 調(diào)用
Result<List<UserDTO>> result = Result.fromJson(json, 
    new TypeReference<Result<List<UserDTO>>>() {});

2. 坑二:工具類封裝時泛型參數(shù)"對不上"

常見寫法

public class ResultUtil {
    
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setData(data);
        result.setCode(200);
        return result;
    }
}
?
// 調(diào)用
List<UserDTO> users = userService.list();
Result<List<UserDTO>> result = ResultUtil.success(users); // 完美

這看起來沒問題。但如果這樣呢?

public static <T> T getData(Result<T> result) {
    if (result.getCode() == 200) {
        return result.getData(); // OK
    }
    return null; // 這里有問題嗎?
}

問題在哪

當 T 是基本類型包裝類(如 Integer、Long)時,返回 null 可能導致 NPE。

正確做法

public static <T> T getDataOrThrow(Result<T> result) {
    if (result.getCode() != 200) {
        throw new BusinessException(result.getMessage());
    }
    return result.getData(); // 非空,編譯器保證
}
?
// 或者返回空對象而非 null
public static <T> T getData(Result<T> result, T defaultValue) {
    if (result.getCode() != 200) {
        return defaultValue;
    }
    return result.getData();
}

3. 坑三:new T()永遠編譯不過

常見寫法

public class BaseService<T> {
    
    public T createEntity() {
        // 想動態(tài)創(chuàng)建實例
        return new T(); // 編譯錯誤!
    }
}

問題在哪

泛型擦除后,運行時根本不知道 T 是什么類型,無法調(diào)用構造函數(shù)。這是 Java 類型系統(tǒng)的限制。

正確做法

方案一:通過 Class 對象創(chuàng)建

public class BaseService<T> {
    
    private final Class<T> entityClass;
    
    public BaseService(Class<T> entityClass) {
        this.entityClass = entityClass;
    }
    
    public T createEntity() {
        try {
            return entityClass.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException("創(chuàng)建實例失敗", e);
        }
    }
}
?
// 使用
UserService userService = new UserService(User.class);
User user = userService.createEntity();

方案二:用反射工具類封裝(推薦)

public class BeanUtil {
    
    public static <T> T newInstance(Class<T> clazz) {
        try {
            return clazz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new IllegalStateException("無法創(chuàng)建實例: " + clazz.getName(), e);
        }
    }
    
    public static <T> T copyProperties(Object source, Class<T> targetClass) {
        T target = newInstance(targetClass);
        BeanUtils.copyProperties(source, target);
        return target;
    }
}

4. 坑四:泛型上限沒設對,導致類型轉換 ClassCastException

常見寫法

// 隨便定義一個泛型
public class DataHolder<T> {
    private T data;
    
    public void process() {
        // 假設需要調(diào)用 Comparable 方法
        Comparable<T> comparable = data; // 可能出問題
        comparable.compareTo(data); // 如果 T 是 String,OK;如果是 User?User 沒實現(xiàn) Comparable
    }
}

問題在哪

沒有約束 T 的上限,任何類型都能傳,但代碼里可能需要特定能力(如 Comparable、Serializable)。

正確做法

明確泛型上限

// 限定 T 必須實現(xiàn) Comparable
public class DataHolder<T extends Comparable<T>> {
    private T data;
    
    public T max(T other) {
        return data.compareTo(other) > 0 ? data : other; // 編譯器保證安全
    }
}
?
// 限定 T 必須實現(xiàn)序列化
public class CacheWrapper<T extends Serializable> {
    private T data;
}
?
// 限定多重上限
public class Processor<T extends Number & Comparable<T>> {
    public double doubleValue(T value) {
        return value.doubleValue();
    }
}

常見場景:Service 層基類

// 基礎 Service,限定 entity 必須繼承 BaseEntity
public abstract class BaseService<T extends BaseEntity, DTO> {
    
    protected abstract Mapper<T> getMapper();
    
    public DTO getById(Long id) {
        T entity = getMapper().selectById(id);
        return convertToDTO(entity); // entity 一定有 id、createTime 等
    }
    
    protected abstract DTO convertToDTO(T entity);
}
?
// 子類實現(xiàn)
public class UserServiceImpl extends BaseService<User, UserDTO> {
    
    @Override
    protected Mapper<User> getMapper() {
        return userMapper;
    }
    
    @Override
    protected UserDTO convertToDTO(User user) {
        // user 一定有 getId(),因為繼承了 BaseEntity
        return UserDTO.builder()
            .id(user.getId())
            .name(user.getName())
            .build();
    }
}

5. 坑五:泛型方法定義錯誤,調(diào)用時類型推斷失敗

常見寫法

public class Converter {
    
    // 以為是泛型方法,實際上不是
    public static T convert(Object source) {
        return (T) source; // 編譯警告,運行時可能 ClassCastException
    }
}
?
// 調(diào)用
String str = Converter.convert(someObject); // 誰知道轉成啥?

問題在哪

這個 T 不是方法級別泛型,而是類級別泛型。如果類沒有聲明 <T>,這里的 T 就是普通的類型參數(shù)(雖然也能編譯,但語義完全錯誤)。

正確做法

正確的泛型方法

public class Converter {
    
    // 正確的泛型方法:<T> 是方法聲明的一部分
    public static <T> T convert(Object source, Class<T> targetClass) {
        if (source == null) {
            return null;
        }
        return targetClass.cast(source);
    }
    
    // 更安全的版本
    public static <T, S> T convert(S source, Function<S, T> converter) {
        if (source == null) {
            return null;
        }
        return converter.apply(source);
    }
}
?
// 使用
String str = Converter.convert(someObject, String.class);
UserDTO dto = Converter.convert(user, UserDTO::toDTO);

復雜場景:返回多種類型的泛型方法

// 業(yè)務場景:統(tǒng)一處理成功/失敗返回
public class ApiResult {
    
    public static <T> T getOrThrow(ApiResponse<T> response) {
        if (!response.isSuccess()) {
            throw new ApiException(response.getCode(), response.getMessage());
        }
        return response.getData();
    }
    
    // 配合 Optional 使用
    public static <T> Optional<T> toOptional(ApiResponse<T> response) {
        if (response.isSuccess() && response.getData() != null) {
            return Optional.of(response.getData());
        }
        return Optional.empty();
    }
}

6. 坑六:泛型通配符? extends和? super傻傻分不清

常見寫法

// 讀取數(shù)據(jù)時用 extends
public void read(List<? extends Object> list) {
    Object item = list.get(0); // 讀 OK
    list.add(new Object()); // 寫?編譯錯誤
}
?
// 寫入數(shù)據(jù)時用 super
public void write(List<? super String> list) {
    list.add("hello"); // 寫 OK
    String item = list.get(0); // 讀?需要強制轉型
}

問題在哪

搞不清 PECS 原則(Producer Extends, Consumer Super):

  • 讀取數(shù)據(jù)(生產(chǎn)者)→ 用 ? extends
  • 寫入數(shù)據(jù)(消費者)→ 用 ? super

正確做法

記住 PECS 原則

// 生產(chǎn)者:用 extends,只能讀
public double sumOfPrices(List<? extends Product> products) {
    double total = 0;
    for (Product p : products) { // 讀 OK
        total += p.getPrice();
    }
    // products.add(new Product()); // 編譯錯誤,不能寫
    return total;
}
?
// 消費者:用 super,只能寫
public void addNumbers(List<? super Integer> list) {
    list.add(1); // 寫 OK
    list.add(2);
    // Integer num = list.get(0); // 讀出來是 Object
}
?
// 既讀又寫?別用通配符
public <T> void copy(List<T> dest, List<? extends T> src) {
    for (T item : src) { // src 是生產(chǎn)者,可以讀
        dest.add(item); // dest 是消費者,可以寫
    }
}

實際業(yè)務場景

// DTO 轉換:源列表是生產(chǎn)者,目標列表是消費者
public <S, T> void convertList(List<S> sources, List<T> targets, 
                                Function<S, T> converter) {
    for (S source : sources) {
        targets.add(converter.apply(source));
    }
}
?
// 使用
List<User> users = userMapper.selectList();
List<UserDTO> dtos = new ArrayList<>();
convertList(users, dtos, User::toDTO);

7. 坑七:泛型與序列化沖突,返回給前端變成了 LinkedHashMap

常見寫法

public class Result<T> {
    private T data;
    
    // 序列化給前端
    public String toJson() {
        return new ObjectMapper().writeValueAsString(this);
    }
}
?
// 接口
@GetMapping("/user")
public Result<UserDTO> getUser() {
    Result<UserDTO> result = new Result<>();
    result.setData(userDTO);
    return result;
}

前端收到的 JSON:

{
  "data": {
    "name": "張三",
    "id": 1
  }
}

這看起來沒問題。但如果前端拿到的是 List<UserDTO> 呢?

問題在哪

當 T 是泛型集合時,Jackson 默認反序列化會丟失具體類型信息,反序列化成 LinkedHashMap 而不是具體 DTO。

// 后端
Result<List<UserDTO>> result = new Result<>();
result.setData(Arrays.asList(userDTO1, userDTO2));
?
// 前端收到
{
  "data": [
    {"name": "張三", "id": 1}, // 不再是 UserDTO
    {"name": "李四", "id": 2}
  ]
}

正確做法

方案一:用 TypeReference 顯式指定泛型

public class Result<T> {
    
    public String toJson() {
        try {
            ObjectMapper mapper = new ObjectMapper();
            mapper.registerModule(new JavaTimeModule());
            return mapper.writeValueAsString(this);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    // 泛型反序列化方法
    public static <T> T fromJson(String json, TypeReference<T> typeRef) {
        try {
            return new ObjectMapper().readValue(json, typeRef);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
?
// 后端給前端:直接序列化,不需要改動
// 前端拿到字符串后:
Result<List<UserDTO>> result = Result.fromJson(jsonString,
    new TypeReference<Result<List<UserDTO>>>() {});

方案二:用 @JsonTypeInfo 標記具體類型

@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
public class Result<T> {
    private T data;
}
?
// 序列化成
{
  "data": {
    "@c": ".UserDTO",
    "name": "張三"
  }
}

方案三:返回 ResponseEntity(Spring 官方推薦)

@GetMapping("/users")
public ResponseEntity<Result<List<UserDTO>>> getUsers() {
    Result<List<UserDTO>> result = Result.success(userService.list());
    return ResponseEntity.ok(result);
}

8. 坑八:泛型嵌套太深,代碼可讀性災難

常見寫法

// 四層泛型嵌套,你能一眼看出 data 是什么嗎?
Response<Result<Page<List<UserDTO>>>> result = userService.query(request);
?
// 訪問數(shù)據(jù)時
List<UserDTO> users = result.getData().getData().getData().getRecords();

問題在哪

泛型是為了類型安全,但如果嵌套太深,反而降低了可讀性,而且修改維護時容易出錯。

正確做法

方案一:抽取中間類型

// 第一層:接口返回統(tǒng)一封裝
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;
}
?
// 第二層:分頁數(shù)據(jù)統(tǒng)一封裝
public class PageResult<T> {
    private List<T> records;
    private long total;
    private int pageNum;
    private int pageSize;
}
?
// 簡化后的調(diào)用
ApiResponse<PageResult<UserDTO>> result = userService.query(request);
PageResult<UserDTO> page = result.getData();
List<UserDTO> users = page.getRecords();

方案二:用 Optional 消除空判斷

public class Result<T> {
    private T data;
    
    public Optional<T> getOptionalData() {
        return Optional.ofNullable(data);
    }
}
?
// 使用
user.getOptionalData()
    .map(PageResult::getRecords)
    .orElse(Collections.emptyList());

方案三:工具方法封裝常用路徑

public class ResultHelper {
    
    public static <T> List<T> getRecordsOrEmpty(Result<PageResult<T>> result) {
        if (result == null || result.getData() == null) {
            return Collections.emptyList();
        }
        return result.getData().getRecords();
    }
}
?
// 調(diào)用
List<UserDTO> users = ResultHelper.getRecordsOrEmpty(result);

最佳實踐總結

坑點問題解決方案
instanceof 失效泛型擦除用 Class 或 TypeReference 判斷
工具類泛型失效泛型參數(shù)對不上顯式傳入 Class 或用函數(shù)式接口
new T() 編譯錯誤類型擦除限制通過 Class.newInstance() 或構造函數(shù)引用
ClassCastException泛型上限未設用 約束
類型推斷失敗泛型方法定義錯誤放在返回類型前
extends/super 混淆PECS 原則不清記?。鹤x用 extends,寫用 super
序列化變成 Map泛型信息丟失用 TypeReference 或 ResponseEntity
泛型嵌套太深可讀性差抽取中間類型 + 工具方法封裝

泛型封裝黃金法則

1. 永遠不要 new T()
2. 永遠不要寫 instance of T
3. 永遠明確泛型上限
4. 永遠記住 PECS 原則
5. 永遠用 TypeReference 處理 JSON 序列化

記住:泛型是給編譯器用的,不是給運行時用的。想清楚這一點,大部分坑都能避開。

到此這篇關于SpringBoot泛型封裝的坑的文章就介紹到這了,更多相關SpringBoot泛型封裝內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關文章

  • SpringBoot?docker項目部署實戰(zhàn)

    SpringBoot?docker項目部署實戰(zhàn)

    本文主要介紹了SpringBoot?docker項目部署實戰(zhàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-08-08
  • 使用Filter實現(xiàn)登錄權限驗證

    使用Filter實現(xiàn)登錄權限驗證

    這篇文章主要為大家詳細介紹了使用Filter實現(xiàn)登錄權限驗證,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • java四種引用及在LeakCanery中應用詳解

    java四種引用及在LeakCanery中應用詳解

    這篇文章主要介紹了java四種引用及在LeakCanery中應用,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-09-09
  • java分割字符串多種方法(附例子)

    java分割字符串多種方法(附例子)

    這篇文章主要給大家介紹了關于java分割字符串多種方法的相關資料,Java中有多種方法可以實現(xiàn)字符串分割,文中將每張方法都給出了代碼示例,需要的朋友可以參考下
    2023-10-10
  • jdbc與druid連接池的使用詳解

    jdbc與druid連接池的使用詳解

    這篇文章主要介紹了jdbc與druid連接池的使用詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • SpringBoot中VO/DTO/PO的具體使用

    SpringBoot中VO/DTO/PO的具體使用

    VO/DTO/PO等實體類中字段常常會存在多數(shù)相同,根據(jù)業(yè)務需求少數(shù)不同,本文主要介紹了SpringBoot中VO/DTO/PO的具體使用,感興趣的可以了解一下
    2024-03-03
  • 關于@ComponentScan?TypeFilter自定義指定掃描bean的規(guī)則

    關于@ComponentScan?TypeFilter自定義指定掃描bean的規(guī)則

    這篇文章主要介紹了關于@ComponentScan?TypeFilter自定義指定掃描bean的規(guī)則,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Springboot實現(xiàn)多線程及線程池監(jiān)控

    Springboot實現(xiàn)多線程及線程池監(jiān)控

    線程池的監(jiān)控很重要,本文就來介紹一下Springboot實現(xiàn)多線程及線程池監(jiān)控,文中通過示例代碼介紹的非常詳細,需要的朋友們下面隨著小編來一起學習學習吧
    2024-01-01
  • 詳解Spring加載Properties配置文件的四種方式

    詳解Spring加載Properties配置文件的四種方式

    這篇文章主要介紹了詳解Spring加載Properties配置文件的四種方式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • idea的language?level默認為5的解決方案

    idea的language?level默認為5的解決方案

    文章介紹了在IDEA中修改項目語言級別(language?level)時遇到的問題,以及解決方法,方法1是通過在父pom文件中添加配置來解決,方法2是修改maven的settings.xml文件
    2025-12-12

最新評論

通江县| 洛扎县| 沁水县| 大理市| 万全县| 贵溪市| 章丘市| 南漳县| 资中县| 弥渡县| 德令哈市| 巴彦县| 南京市| 江城| 红桥区| 桦川县| 满洲里市| 宜良县| 永昌县| 海淀区| 铜山县| 琼中| 贡嘎县| 海南省| 望城县| 安图县| 晋州市| 大方县| 安阳市| 滦南县| 营山县| 木兰县| 体育| 四川省| 新昌县| 庆城县| 巩义市| 南开区| 辰溪县| 西藏| 比如县|