一、为什么要用泛型?(不使用泛型有多痛)
在没有泛型之前,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 正例:使用泛型后
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 擦除规则
- 无限制
<T>→ 擦为Object - 有限制
<? extends T>→ 擦为T
这就是泛型很多限制的根本原因!
五、泛型常见限制(必须记住)
- 不能使用基本类型 (
int/long)→ 必须用包装类 - 不能创建泛型数组 :
T[] arr = new T[10]❌ - 不能捕获泛型异常
- 静态方法不能用类泛型
- 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
当前角色:管理员
执行管理员操作
八、面试高频题(背会稳过)
-
Java 泛型的原理是什么? 答:类型擦除,编译后泛型信息消失,字节码无泛型,运行时都是原始类型。
-
List<Object>和List<?>区别? 答:List<Object>可以添加任意对象;List<?>只能读不能写。 -
extends和super区别? 答:extends只读(生产者);super可写(消费者)。 -
**泛型有哪些限制?为什么?**答:因为类型擦除,不能用基本类型、不能创建泛型数组、不能判断泛型类型。
九、总结
- 泛型 = 类型安全 + 代码复用 + 自动推导
- 三大语法:泛型类、泛型接口、泛型方法
- 三大通配符:
<?>、<? extends T>、<? super T> - 核心原理:类型擦除
- 企业最爱用:统一返回泛型 R<T>
- 泛型 + 枚举 = 最优雅的业务常量方案