引言

Java开发中,泛型(Generics)是保障代码安全性、提升复用性的关键技术之一。它通过在编译阶段进行类型校验,彻底解决了传统集合框架中频繁类型转换导致的ClassCastException问题,同时让一套逻辑能够灵活适配多种数据类型,极大简化了代码结构。

很多开发者对泛型的认知停留在用<T>代替具体类型,但泛型的核心价值远不止于此。它本质是一种类型参数化机制,将类型从硬编码变为动态传入,从而实现类型安全与代码复用的双重目标。

泛型通过以下机制解决这些问题:
  • 编译期类型校验:在定义集合(如List<User>)时指定类型,编译器自动拦截不符合类型的数据添加操作;
  • 自动类型推断:无需手动强制转换,list.get(0)直接返回指定类型(如User),减少代码冗余;
  • 逻辑复用:一套泛型类/方法(如Result<T>)可适配所有数据类型,避免重复开发。

语法形式

定位

作用

示例

<T>

类型参数声明

定义泛型变量,代表后续可传入的具体类型(T为约定俗成的占位符,可替换为E(元素)、K(键)、V(值)等)

定义泛型类:class Box<T>;定义泛型方法:public <T> T get()

List<T>

参数化类型

表示特定类型的集合/对象,明确泛型变量的具体应用场景

List<User>

(存储用户的列表)、Map<String, User>(键为字符串、值为用户的映射)

<T extends Number>

类型边界限制

约束泛型变量的范围,仅允许传入指定类及其子类,避免类型滥用

定义仅支持数字类型的泛型方法public <T extends Number> double sum(List<T> list)

实战场景

场景 1:API 统一返回结果封装(解决多类型结果冗余问题)

Web接口返回结果需包含状态码(code)、提示信息(msg)和业务数据(data),若为每个业务类型(如用户、订单、凭证)单独编写结果类(UserResultOrderResult),会产生大量重复代码,且新增业务类型时需同步新增结果类,违反开闭原则。

定义泛型结果类Result<T>,用T表示动态变化的业务数据类型,通过静态工厂方法简化调用。

/**
 * 通用API返回结果类
 * @param <T> 业务数据类型(如User、List<Order>、Boolean等)
 */
public class Result<T> {
    // 状态码:200(成功)、500(服务器错误)、400(参数错误)等
    private int code;
    // 提示信息:如“操作成功”“用户不存在”
    private String msg;
    // 业务数据:泛型类型,适配任意数据结构
    private T data;

    // 私有构造方法:避免外部直接创建,统一通过工厂方法调用
    private Result(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    // 工厂方法:成功场景(无提示信息,默认“success”)
    public static <T> Result<T> success(T data) {
        return new Result<>(200, "success", data);
    }

    // 重载工厂方法:成功场景(自定义提示信息)
    public static <T> Result<T> success(String msg, T data) {
        return new Result<>(200, msg, data);
    }

    // 工厂方法:失败场景(自定义状态码和提示信息)
    public static <T> Result<T> fail(int code, String msg) {
        // 失败场景无业务数据,data设为null
        return new Result<>(code, msg, null);
    }

    // getter方法(省略setter:结果类为不可变对象,避免后续修改)
    public int getCode() { return code; }
    public String getMsg() { return msg; }
    public T getData() { return data; }
}

示例:

// 1. 返回单个用户信息(data为User类型)
User user = userService.getById(1L);
return Result.success(user);

// 2. 返回订单列表(data为List<Order>类型)
List<Order> orderList = orderService.listByUserId(1L);
return Result.success("查询订单成功", orderList);

// 3. 返回布尔状态(如“是否已收藏”,data为Boolean类型)
boolean isCollected = collectService.check(1L, 2L);
return Result.success(isCollected);

// 4. 返回失败结果(无业务数据)
if (user == null) {
    return Result.fail(404, "用户不存在");
}

方案优势:

  • 不可变设计:私有构造方法+仅提供getter,避免结果对象被后续修改,保障数据安全性;
  • 灵活重载:通过工厂方法重载,适配默认提示自定义提示失败场景等不同需求;
  • 类型安全:编译器自动校验data类型,若将User传入Result<List<Order>>会直接编译报错,避免运行时异常。
场景 2:分页查询结果封装(解决分页逻辑重复问题)

分页查询是业务系统的基础需求,返回结果通常包含总条数(total)、当前页数据(list)、页码(pageNum)、页大小(pageSize)。若为每个业务类型编写分页结果类(UserPageResultVoucherPageResult),会导致分页逻辑与业务类型强耦合,新增业务时需重复编写分页结构。

定义泛型分页类PageResult<T>,用T表示当前页数据的类型,统一分页结构,适配所有业务的分页需求。

/**
 * 通用分页查询结果类
 * @param <T> 当前页数据的类型(如User、Voucher等)
 */
public class PageResult<T> {
    // 总记录数:满足查询条件的所有数据条数
    private long total;
    // 当前页数据列表:泛型类型,适配不同业务数据
    private List<T> list;
    // 当前页码:如第1页、第2页
    private int pageNum;
    // 页大小:每页显示的数据条数,如10条/页
    private int pageSize;

    // 构造方法:直接暴露,便于外部传入分页参数
    public PageResult(long total, List<T> list, int pageNum, int pageSize) {
        this.total = total;
        this.list = list;
        this.pageNum = pageNum;
        this.pageSize = pageSize;
    }

    // 静态工厂方法:简化对象创建(可选,根据团队习惯选择)
    public static <T> PageResult<T> of(long total, List<T> list, int pageNum, int pageSize) {
        return new PageResult<>(total, list, pageNum, pageSize);
    }

    // getter方法(省略setter:分页结果为查询结果,无需后续修改)
    public long getTotal() { return total; }
    public List<T> getList() { return list; }
    public int getPageNum() { return pageNum; }
    public int getPageSize() { return pageSize; }
}
    场景 3:策略模式 + 泛型:通用数据处理器(解决多类型业务逻辑耦合问题)

    系统中有多种凭证类型(电子券、纸质券、红包),每种凭证需执行校验合法性→发放凭证的逻辑,但不同凭证的校验规则和发放方式不同。若用if-else判断凭证类型并执行对应逻辑,会导致代码臃肿),新增凭证类型时需修改原有逻辑,违反开闭原则。

    结合策略模式,定义泛型处理器接口Handler<T>,为每种凭证类型实现独立的处理器,再通过处理器工厂动态匹配类型,实现新增类型仅需新增处理器的解耦效果。

    /**
     * 通用数据处理器接口(策略模式核心)
     * @param <T> 处理的数据类型(如Ecoupon、PaperVoucher等)
     */
    public interface Handler<T> {
        /**
         * 判断当前处理器是否支持该数据类型
         * @param clazz 数据类型的Class对象
         * @returntrue:支持;false:不支持
         */
        boolean supports(Class<?> clazz);
    
        /**
         * 处理核心业务逻辑(校验+发放)
         * @param data 待处理的数据(泛型类型,确保类型安全)
         */
        void handle(T data);
    }

      实现具体凭证处理器(策略实现):

      // 电子券处理器
      @Component // 注入Spring容器,便于后续工厂自动获取
      public class EcouponHandler implements Handler<Ecoupon> {
          @Override
          public boolean supports(Class<?> clazz) {
              // 判断传入的Class是否为Ecoupon或其子类
              return Ecoupon.class.isAssignableFrom(clazz);
          }
      
          @Override
          public void handle(Ecoupon ecoupon) {
              // 1. 电子券合法性校验(如券码是否有效、是否过期)
              validateEcoupon(ecoupon);
              // 2. 电子券发放逻辑(如写入用户券包、发送短信通知)
              issueEcoupon(ecoupon);
          }
      
          // 电子券专属校验逻辑
          private void validateEcoupon(Ecoupon ecoupon) {
              if (ecoupon.getExpireTime().before(new Date())) {
                  throw new IllegalArgumentException("电子券已过期:" + ecoupon.getCode());
              }
          }
      
          // 电子券专属发放逻辑
          private void issueEcoupon(Ecoupon ecoupon) {
              System.out.println("电子券发放成功:券码=" + ecoupon.getCode() + ",用户ID=" + ecoupon.getUserId());
          }
      }
      
      // 纸质券处理器(同理,仅实现纸质券专属逻辑)
      @Component
      public class PaperVoucherHandler implements Handler<PaperVoucher> {
          @Override
          public boolean supports(Class<?> clazz) {
              return PaperVoucher.class.isAssignableFrom(clazz);
          }
      
          @Override
          public void handle(PaperVoucher paperVoucher) {
              // 纸质券校验(如序列号是否存在)
              validatePaperVoucher(paperVoucher);
              // 纸质券发放(如记录发放日志、通知线下门店)
              issuePaperVoucher(paperVoucher);
          }
      
          private void validatePaperVoucher(PaperVoucher paperVoucher) {
              if (paperVoucher.getSerialNo() == null || paperVoucher.getSerialNo().length() != 10) {
                  throw new IllegalArgumentException("纸质券序列号无效:" + paperVoucher.getSerialNo());
              }
          }
      
          private void issuePaperVoucher(PaperVoucher paperVoucher) {
              System.out.println("纸质券发放成功:序列号=" + paperVoucher.getSerialNo() + ",用户ID=" + paperVoucher.getUserId());
          }
      }

        实现处理器工厂(动态匹配策略):

        @Service
        public class HandlerManager {
            // 自动注入Spring容器中所有Handler实现类(泛型通配符<?>表示“任意类型的Handler”)
            @Autowired
            private List<Handler<?>> handlers;
        
            /**
             * 通用处理方法:根据数据类型动态匹配处理器并执行逻辑
             * @param data 待处理的数据(泛型类型T)
             * @param <T> 数据类型
             */
            @SuppressWarnings("unchecked") // 抑制“未检查的转换”警告(通过supports校验,转换安全)
            public <T> void process(T data) {
                if (data == null) {
                    throw new IllegalArgumentException("待处理数据不能为空");
                }
                // 获取数据的Class对象(如Ecoupon.class)
                Class<?> clazz = data.getClass();
                // 遍历所有处理器,找到支持当前类型的处理器
                for (Handler<?> handler : handlers) {
                    if (handler.supports(clazz)) {
                        // 将Handler<?>强制转为Handler<T>(通过supports校验,类型匹配)
                        ((Handler<T>) handler).handle(data);
                        return; // 找到匹配的处理器后立即执行,避免后续遍历
                    }
                }
                // 无匹配处理器时抛出异常,便于快速定位问题
                throw new UnsupportedOperationException("无支持该类型的处理器:" + clazz.getName());
            }
        }

        示例:

        @Service
        public class VoucherService {
            @Autowired
            private HandlerManager handlerManager;
        
            /**
             * 发放凭证(统一入口,无需关注具体凭证类型)
             * @param voucher 待发放的凭证(泛型父类,如Voucher)
             */
            public void issueVoucher(Voucher voucher) {
                // 调用处理器工厂,动态匹配并执行逻辑
                handlerManager.process(voucher);
            }
        }
        
        // 调用示例
        public class Test {
            public static void main(String[] args) {
                VoucherService voucherService = new VoucherService(); // 实际开发中通过Spring注入
        
                // 1. 发放电子券(自动匹配EcouponHandler)
                Ecoupon ecoupon = new Ecoupon("E20240901", 1L, new Date(System.currentTimeMillis() + 86400000));
                voucherService.issueVoucher(ecoupon);
        
                // 2. 发放纸质券(自动匹配PaperVoucherHandler)
                PaperVoucher paperVoucher = new PaperVoucher("P2024090101", 1L);
                voucherService.issueVoucher(paperVoucher);
            }
        }

        方案优势:

        • 完全解耦:业务逻辑(处理器)与类型判断(工厂)分离,新增凭证类型(如红包)时,仅需新增RedPacketHandler,无需修改HandlerManagerVoucherService
        • 类型安全:通过supports方法校验类型,避免处理器与数据类型不匹配的问题;
        • 易于维护:每个处理器仅关注一种凭证类型的逻辑,代码结构清晰,排查问题更高效。
        场景 4:JSON 泛型反序列化(解决泛型擦除导致的类型丢失问题)

        使用GsonJacksonJSON框架反序列化泛型集合(如List<User>Map<String, Order>)时,直接调用fromJson(json, List.class)会返回List<Object>,需手动强制转换,不仅代码冗余,还存在ClassCastException风险。这是因为Java泛型存在类型擦除机制 —— 编译后泛型信息会被擦除(如List<User>变为List),导致JSON框架无法识别具体类型。

        通过TypeReference类保留泛型信息。TypeReference利用匿名子类会保留父类泛型参数的特性,在运行时获取泛型的具体类型,从而让JSON框架正确反序列化。

        /**
         * 泛型类型引用类:用于保留泛型信息,解决JSON反序列化问题
         * @param <T> 泛型类型(如List<User>、Map<String, Order>)
         */
        public abstract class TypeReference<T> {
            // 存储泛型的具体类型(如List<User>对应的Type对象)
            private final Type type;
        
            protected TypeReference() {
                // 1. 获取当前匿名子类的父类(即TypeReference<T>)
                Type superClass = getClass().getGenericSuperclass();
                // 2. 判断父类是否为参数化类型(即是否包含泛型信息)
                if (!(superClass instanceof ParameterizedType)) {
                    throw new IllegalStateException("TypeReference必须使用匿名子类形式创建,如new TypeReference<List<User>>() {}");
                }
                // 3. 提取泛型的具体类型(getActualTypeArguments()[0]获取第一个泛型参数)
                this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
            }
        
            // 对外提供泛型类型,供JSON框架使用
            public Type getType() {
                returntype;
            }
        }

        封装JSON工具类:

        public class JsonUtil {
            // 单例Gson对象(避免频繁创建,提升性能)
            private static final Gson GSON = new Gson();
        
            /**
             * 泛型反序列化:将JSON字符串转为指定泛型类型的对象
             * @param json JSON字符串
             * @param typeReference 泛型类型引用(如new TypeReference<List<User>>() {})
             * @param <T> 目标泛型类型
             * @return 反序列化后的对象
             */
            public static <T> T fromJson(String json, TypeReference<T> typeReference) {
                if (json == null || json.isEmpty()) {
                    throw new IllegalArgumentException("JSON字符串不能为空");
                }
                // 调用Gson的fromJson方法,传入保留泛型信息的Type对象
                return GSON.fromJson(json, typeReference.getType());
            }
        
            // 其他JSON工具方法(如序列化)可在此补充
        }

        示例:

        // 1. 反序列化List<User>
        String userJson = "[{\"id\":1,\"name\":\"Alice\",\"age\":25},{\"id\":2,\"name\":\"Bob\",\"age\":28}]";
        List<User> userList = JsonUtil.fromJson(userJson, new TypeReference<List<User>>() {});
        
        // 2. 反序列化Map<String, Order>(键为订单ID,值为订单对象)
        String orderJson = "{\"1001\":{\"orderId\":1001,\"amount\":99.9},\"1002\":{\"orderId\":1002,\"amount\":199.9}}";
        Map<String, Order> orderMap = JsonUtil.fromJson(orderJson, new TypeReference<Map<String, Order>>() {});
        
        // 3. 反序列化泛型结果类Result<List<Voucher>>
        String resultJson = "{\"code\":200,\"msg\":\"success\",\"data\":[{\"voucherId\":1,\"code\":\"E20240901\"}]}";
        Result<List<Voucher>> voucherResult = JsonUtil.fromJson(resultJson, new TypeReference<Result<List<Voucher>>>() {});
        场景 5:泛型工具类:对象拷贝(解决多类型对象转换冗余问题)

        业务开发中频繁需要对象转换(如User(数据库实体)→UserDTO(接口传输对象)、VoucherVoucherVO(前端展示对象)),若为每个转换场景编写copyUserToDTOcopyVoucherToVO等方法,会导致代码冗余,且转换逻辑分散,维护困难。

        定义泛型对象拷贝工具类,通过反射或第三方框架(如 Spring BeanUtilsMapStruct)实现任意类型对象的拷贝,一套逻辑适配所有转换场景。

        /**
         * 通用对象拷贝工具类
         */
        public class ConvertUtil {
            /**
             * 泛型对象拷贝:将源对象的属性拷贝到目标对象(浅拷贝)
             * @param source 源对象(如User)
             * @param targetClass 目标对象的Class(如UserDTO.class)
             * @param <S> 源对象类型
             * @param <T> 目标对象类型
             * @return 拷贝后的目标对象
             */
            public static <S, T> T copy(S source, Class<T> targetClass) {
                // 校验源对象和目标类是否为空
                if (source == null) {
                    throw new IllegalArgumentException("源对象不能为空");
                }
                if (targetClass == null) {
                    throw new IllegalArgumentException("目标类不能为空");
                }
        
                try {
                    // 1. 创建目标对象实例(通过无参构造方法)
                    T target = targetClass.getDeclaredConstructor().newInstance();
                    // 2. 拷贝属性:Spring BeanUtils支持同名同类型属性的拷贝
                    BeanUtils.copyProperties(source, target);
                    return target;
                } catch (Exception e) {
                    // 3. 异常封装:明确异常原因,便于排查问题
                    throw new RuntimeException("对象拷贝失败:源类型=" + source.getClass().getName() + ",目标类型=" + targetClass.getName(), e);
                }
            }
        
            /**
             * 泛型集合拷贝:将源集合中的每个元素拷贝到目标类型,返回目标类型的集合
             * @param sourceList 源集合(如List<User>)
             * @param targetClass 目标元素类型(如UserDTO.class)
             * @param <S> 源元素类型
             * @param <T> 目标元素类型
             * @return 目标类型的集合(如List<UserDTO>)
             */
            public static <S, T> List<T> copyList(List<S> sourceList, Class<T> targetClass) {
                if (sourceList == null || sourceList.isEmpty()) {
                    return Collections.emptyList(); // 返回空集合,避免NullPointerException
                }
        
                // 遍历源集合,逐个拷贝元素到目标类型
                return sourceList.stream()
                        .map(source -> copy(source, targetClass))
                        .collect(Collectors.toList());
            }
        }

        示例:

        // 1. 单个对象拷贝:User → UserDTO
        User user = new User(1L, "yian", 25, "yian@example.com");
        UserDTO userDTO = ConvertUtil.copy(user, UserDTO.class);
        
        // 2. 集合拷贝:List<Voucher> → List<VoucherVO>
        List<Voucher> voucherList = voucherService.list();
        List<VoucherVO> voucherVOList = ConvertUtil.copyList(voucherList, VoucherVO.class);
        
        // 3. 复杂场景:PageResult<Voucher> → PageResult<VoucherVO>
        PageResult<Voucher> voucherPage = voucherService.page(1, 10);
        List<VoucherVO> voucherVOPageList = ConvertUtil.copyList(voucherPage.getList(), VoucherVO.class);
        PageResult<VoucherVO> voucherVOPage = new PageResult<>(voucherPage.getTotal(), voucherVOPageList, voucherPage.getPageNum(), voucherPage.getPageSize());

        Spring BeanUtils 基于反射实现,性能较低。若需处理大量对象拷贝(如批量接口),建议替换为MapStruct(编译期生成拷贝代码,性能接近手写),但泛型封装逻辑可保持不变(只需将BeanUtils.copyProperties替换为MapStruct的映射方法);

        高级技巧

        泛型通配符(?)是泛型的进阶特性,很多开发者因不理解其设计意图而滥用,导致代码出现不必要的限制或安全隐患。实际上,通配符的核心是控制泛型类型的范围,根据数据读取数据写入的场景,可分为上界通配符和下界通配符,两者遵循明确的读写规则。

        上界通配符:<? extends T>(能读不能写)
        • 语法含义:表示T及其子类,即泛型类型只能是T或T的子类(如<? extends Number>表示Number、Integer、Double等);
        • 核心场景:仅需从集合中读取数据,无需写入数据;
        • 读写规则:

        可读:可以安全地将集合中的元素转为T类型(因为所有元素都是T的子类,向上转型安全);

        不可写:不能向集合中添加任何非null元素(因为无法确定集合的具体类型,如List<? extends Number>可能是List<Integer>List<Double>,添加IntegerList<Double>会导致类型错误)。

        示例:

        /**
         * 计算数字集合的总和(仅读取数据,不写入)
         * @param numbers 数字集合(支持Integer、Double等Number子类)
         * @return 总和
         */
        public double sum(List<? extends Number> numbers) {
            double total = 0.0;
            for (Number num : numbers) {
                // 读取数据:安全,所有元素都是Number的子类,可转为Number类型
                total += num.doubleValue();
            }
            return total;
        }
        
        // 使用示例
        List<Integer> intList = Arrays.asList(1, 2, 3);
        List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
        System.out.println(sum(intList)); // 输出6.0
        System.out.println(sum(doubleList)); // 输出6.6
        
        // 写入限制(编译报错)
        numbers.add(4); // ❌ 错误:无法确定集合是List<Integer>还是List<Double>
        numbers.add(new Integer(4)); // ❌ 错误:同上,可能与集合实际类型冲突
        numbers.add(null); // ✅ 允许:null是所有类型的默认值,不会导致类型错误(但实际开发中很少用)
        下界通配符:<? super T>(能写不能读)
        • 语法含义:表示T及其父类,即泛型类型只能是T或T的父类(如<? super Integer>表示IntegerNumberObject等);
        • 核心场景:仅需向集合中写入数据,无需读取数据(或仅需读取为Object类型);
        • 读写规则:

        可写:可以安全地向集合中添加T类型的元素(因为T是集合类型的子类,向下转型安全,如List<? super Integer>可以是List<Number>,添加IntegerList<Number>安全);

        可读:只能将集合中的元素转为Object类型(因为无法确定集合的具体类型,如List<? super Integer>可能是List<Number>List<Object>,无法确定元素的具体类型)。

        示例:

        /**
         * 向集合中添加整数(仅写入数据,不读取)
         * @param list 整数或其父类的集合(如List<Integer>、List<Number>、List<Object>)
         */
        public void addIntegers(List<? super Integer> list) {
            // 写入数据:安全,Integer是集合类型的子类
            list.add(100);
            list.add(200);
        
            // 读取限制
            Object obj = list.get(0); // ✅ 允许:只能转为Object类型
            Integer num = list.get(0); // ❌ 错误:无法确定集合是List<Number>还是List<Object>
        }
        
        // 使用示例
        List<Number> numberList = new ArrayList<>();
        List<Object> objectList = new ArrayList<>();
        addIntegers(numberList); // 向List<Number>添加Integer,安全
        addIntegers(objectList); // 向List<Object>添加Integer,安全
        
        System.out.println(numberList); // 输出[100, 200]
        System.out.println(objectList); // 输出[100, 200]
        Logo

        Agent 垂直技术社区,欢迎活跃、内容共建。

        更多推荐