Java 泛型 (Generic) 入门到精通:语法 + 原理 + 实战 + 避坑

一、为什么要用泛型?(不使用泛型有多痛)

在没有泛型之前,Java 集合只能用Object存储任意类型数据,强制类型转换随处可见,代码既不安全又难读,运行时才会报错!

1.1 反例:不使用泛型的代码

java 复制代码
public class GenericTest {
    public static void main(String[] args) {
        // 不使用泛型,默认存储 Object
        List list = new ArrayList();
        list.add("Java");
        list.add(123); 
        list.add(true);

        // 取值必须强转
        String str = (String) list.get(0);
        // 运行时报错!ClassCastException
        String num = (String) list.get(1); 
    }
}

痛点

  1. 类型不安全:可以随便放任意类型数据
  2. 强转繁琐:取值必须手动强转
  3. 报错延迟:编译不报错,运行才崩溃
  4. 可读性差:不知道集合里存的是什么

1.2 正例:使用泛型后

java 复制代码
public class GenericTest {
    public static void main(String[] args) {
        // 限定只能存 String
        List<String> list = new ArrayList<>();
        list.add("Java");
        // list.add(123); 编译直接报错!
        
        // 无需强转
        String str = list.get(0);
    }
}

泛型优势 :✅ 编译期类型检查 ,提前拦截错误✅ 自动类型推导 ,无需强制转换✅ 代码更通用 ,一套代码支持多种类型✅ 可读性更强,一眼知道数据类型


二、泛型基础语法(入门必看)

2.1 什么是泛型?

泛型 = 参数化类型 把数据类型当作参数传递,让类 / 接口 / 方法可以支持任意类型,同时保留类型安全。

2.2 泛型的三种使用场景

1)泛型类
java 复制代码
// T 是类型参数,可以随便写:T/E/K/V 都行
public class Box<T> {
    private T data;

    public void setData(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }
}

// 使用
Box<String> box = new Box<>();
box.setData("泛型");
String data = box.getData();
2)泛型接口
java 复制代码
public interface Result<T> {
    T getData();
}

// 实现类指定类型
public class StringResult implements Result<String> {
    @Override
    public String getData() {
        return "success";
    }
}
3)泛型方法
java 复制代码
public class GenericMethod {
    // 泛型方法:<T> 声明泛型
    public static <T> T getFirst(T[] array) {
        if (array == null || array.length == 0) {
            return null;
        }
        return array[0];
    }

    public static void main(String[] args) {
        String[] strs = {"A", "B"};
        String first = getFirst(strs);
    }
}

三、泛型通配符 ?(重点难点)

3.1 无界通配符 <?>

表示任意类型 ,常用于只读场景

java 复制代码
public static void printList(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

⚠ 注意:<?> 不能添加数据(除了 null)。

3.2 上界通配符 <? extends 类>

只允许 本类 / 子类可读不可写

java 复制代码
// 只能接收 Number 及其子类:Integer、Double 等
public static void getNum(List<? extends Number> list) {
}

3.3 下界通配符 <? super 类>

只允许 本类 / 父类可写可读

java 复制代码
// 只能接收 Integer 及其父类:Number、Object
public static void addNum(List<? super Integer> list) {
    list.add(123);
}

3.4 通配符口诀(背会不踩坑)

  • <? extends T> 生产(读) 协变 → 只能取,不能存
  • <? super T> 消费(写) 逆变 → 可以存,可以取
  • <?> 任意类型 → 只能取,不能存

四、泛型核心原理:类型擦除(面试必考)

4.1 什么是类型擦除?

Java 泛型是伪泛型编译后泛型信息会全部消失,字节码中没有泛型!

java 复制代码
List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
// 输出 true!泛型类型被擦除了
System.out.println(strList.getClass() == intList.getClass());

4.2 擦除规则

  1. 无限制 <T> → 擦为 Object
  2. 有限制 <? extends T> → 擦为 T

这就是泛型很多限制的根本原因!


五、泛型常见限制(必须记住)

  1. 不能使用基本类型int/long)→ 必须用包装类
  2. 不能创建泛型数组T[] arr = new T[10]
  3. 不能捕获泛型异常
  4. 静态方法不能用类泛型
  5. instanceof 不能判断泛型

六、企业级实战:泛型统一接口返回

这是后端开发最常用的泛型实战,统一返回结果,一套模板适配所有接口!

java 复制代码
// 泛型统一返回类
public class R<T> {
    private int code;
    private String msg;
    private T data;

    // 成功
    public static <T> R<T> ok(T data) {
        R<T> r = new R<>();
        r.code = 200;
        r.msg = "成功";
        r.data = data;
        return r;
    }

    // 失败
    public static <T> R<T> fail(String msg) {
        R<T> r = new R<>();
        r.code = 500;
        r.msg = msg;
        return r;
    }

    // getter/setter
}

// 接口使用
@RestController
public class UserController {
    @GetMapping("/user")
    public R<User> getUser() {
        User user = new User(1, "Java泛型");
        return R.ok(user);
    }
}

七、结合你给的枚举代码:泛型 + 枚举 最佳实践

你提供的枚举代码非常标准,我们可以用泛型做一个通用枚举工具类,让所有枚举都能复用!

7.1 定义通用枚举接口

java 复制代码
public interface BaseEnum {
    String getDesc();
}

7.2 你的枚举实现该接口

java 复制代码
public enum UserRole implements BaseEnum {
    ADMIN("管理员"),
    USER("普通用户");

    private final String desc;

    UserRole(String desc) {
        this.desc = desc;
    }

    @Override
    public String getDesc() {
        return desc;
    }
}

7.3 泛型工具类(所有枚举通用)

java 复制代码
public class EnumUtil {
    // 泛型方法:通用获取枚举描述
    public static <T extends BaseEnum> String getDesc(T t) {
        return t == null ? "" : t.getDesc();
    }
}

7.4 最终业务代码

java 复制代码
public class UserService {
    public void setUserRole(UserRole role) {
        String desc = EnumUtil.getDesc(role);
        System.out.println("当前角色:" + desc);

        if (UserRole.ADMIN == role) {
            System.out.println("执行管理员操作");
        } else {
            System.out.println("执行普通用户操作");
        }
    }

    public static void main(String[] args) {
        UserService service = new UserService();
        service.setUserRole(UserRole.ADMIN);
    }
}

输出

plaintext

java 复制代码
当前角色:管理员
执行管理员操作

八、面试高频题(背会稳过)

  1. Java 泛型的原理是什么? 答:类型擦除,编译后泛型信息消失,字节码无泛型,运行时都是原始类型。

  2. List<Object>List<?> 区别? 答:List<Object>可以添加任意对象;List<?>只能读不能写。

  3. extendssuper 区别? 答:extends 只读(生产者);super 可写(消费者)。

  4. **泛型有哪些限制?为什么?**答:因为类型擦除,不能用基本类型、不能创建泛型数组、不能判断泛型类型。


九、总结

  1. 泛型 = 类型安全 + 代码复用 + 自动推导
  2. 三大语法:泛型类、泛型接口、泛型方法
  3. 三大通配符:<?><? extends T><? super T>
  4. 核心原理:类型擦除
  5. 企业最爱用:统一返回泛型 R<T>
  6. 泛型 + 枚举 = 最优雅的业务常量方案
相关推荐
2301_803538951 小时前
SQL如何避免不同团队修改同一张表_基于前缀名的授权GRANT ON语法
jvm·数据库·python
艾莉丝努力练剑2 小时前
【Linux线程】Linux系统多线程(七):<线程同步与互斥>线程同步(下)
java·linux·运维·服务器·c++·学习·操作系统
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【15】工具执行拦截器(ToolInterceptor)
java·人工智能·spring
m0_678485452 小时前
c++怎么在Windows下设置文件的安全访问控制列表(ACL)权限【底层】
jvm·数据库·python
ch.ju2 小时前
Java程序设计(第3版)第二章——逻辑运算符
java
喜欢流萤吖~2 小时前
SpringBoot 异步处理与线程池实战
java·开发语言
大罗LuoSir2 小时前
分布式微服务全貌了解-整体架构、特征和需关注解决的问题
java·缓存·微服务·zookeeper·容器·服务发现·负载均衡
2301_817672262 小时前
Go语言怎么做六边形架构_Go语言六边形架构教程【简明】
jvm·数据库·python
Dshuishui2 小时前
学习一下 Python 包管理器 uv
开发语言·python·uv