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. 泛型 + 枚举 = 最优雅的业务常量方案
相关推荐
笨鸟先飞,勤能补拙几秒前
AI 赋能网络安全领域深度剖析
网络·人工智能·windows·安全·web安全·网络安全·github
卷无止境8 分钟前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境14 分钟前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd12317 分钟前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102161 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
我上去就是一套QWER然后等待复活1 小时前
Windows 清理 CC Switch 并恢复官方 Codex CLI 登录流程(完整记录)
windows
bamb001 小时前
一个项目带你入门AI应用开发01
python
0566461 小时前
Python康复训练——常用标准库
开发语言·python·学习
极光代码工作室2 小时前
基于SpringBoot的课程预约系统
java·springboot·web开发·后端开发
吾儿良辰2 小时前
一个被BCL遗忘的高性能集合:C# CircularBuffer<T>深度解析
开发语言·windows·c#