Java泛型详解:类型安全与代码复用
一,什么是泛型
泛型(Generics) 是 Java SE 5 引入的重要特性,它允许你在定义类、接口和方法时使用“类型参数”。简单来说,泛型就是把类型当作参数传递。它的核心目的是实现类型安全和代码复用,让你在编译阶段就能发现类型不匹配的问题,而不是等到程序运行并崩溃时才发现
泛型的本质是参数化类型。也就是说,你可以在定义类、接口或方法时,把“具体的类型”变成一个可以被替换的“变量”。
二,泛型的作用
1,泛型的作用
-
类型安全 (Type Safety):把错误留在编译期。如果你试图往
List<String>里放一个整数,编译器会立刻报错,而不是等程序运行时才崩溃。 -
消除强制转换 (Eliminate Casting):代码更简洁。不需要再写
(String) list.get(0)这种啰嗦的代码了,编译器知道它就是 String。 -
代码复用 (Reusability):一套逻辑,百种类型。你可以写一个通用的“排序”或“存储”逻辑,然后复用到 Integer、String 等不同类型上。
泛型可以应用于类、接口和方法。常见的占位符包括 T (Type), E (Element), K (Key), V (Value)。
三,泛型的种类
了解泛型首先了解泛型的结构----通配符
泛型的通配符
| 语法 | 术语 | 形象描述 |
<?> |
无界通配符 | “随便什么都行”。只读,不能往里写数据。 |
<? extends T> |
上界通配符 | “T 及其子类”。比如 <? extends Number> 接受 Integer、Double。通常用于“读取”。 |
<? super T> |
下界通配符 | “T 及其父类”。通常用于“写入”。 |
泛型的语法
| 顺序 | 专业名称 | 语法特征 | 核心作用 |
| 01 | 泛型接口 | interface Name<T> |
定义一套通用的行为标准。 |
| 02 | 受限类型泛型类 | class Name<T extends Number> |
限制类型范围,确保可以使用特定父类的方法。 |
| 03 | 泛型方法 | <E> void method(E e) |
独立于类之外的类型灵活性,静态方法必备。 |
| 04 | 上界通配符 | <? extends T> |
安全读取:允许 T 及其子类。 |
| 05 | 下界通配符 | <? super T> |
安全写入:允许 T 及其父类。 |
1,为什么需要泛型
在没有泛型之前,程序员只能用 Object 来代表一切,但这会带来两个大麻烦:
类型转换很累:每次拿出来都要强转 (String) list.get(0)。
容易崩溃:如果不小心往装 String 的集合里塞了个 Integer,编译时不报错,一运行就抛出
ClassCastException
// 1. 无泛型的List:默认存储Object类型,能装任意数据
List list = new ArrayList();
// 2. 往“本意存字符串”的集合里塞了整数,编译完全不报错(坑1)
list.add("Java");
list.add("Python");
list.add(123); // 错误插入Integer,但编译器不提示
// 3. 取值时必须强制类型转换,代码冗余且麻烦(坑2)
String str1 = (String) list.get(0); // 正常
String str2 = (String) list.get(1); // 正常
// 4. 运行时崩溃:Integer转String,抛出ClassCastException
String str3 = (String) list.get(2);
// 1. 声明泛型List<String>:明确只能存字符串
List<String> list = new ArrayList<>();
// 2. 编译期就拦截错误:往String集合塞Integer,直接报错(解决“容易崩溃”)
list.add("Java");
list.add("Python");
// list.add(123); // 编译器直接标红:不兼容的类型,无法编译
// 3. 取值时无需强制转换,直接拿到String(解决“类型转换累”)
String str1 = list.get(0);
String str2 = list.get(1);
System.out.println(str1 + ", " + str2); // 输出:Java, Python
2,理解什么是泛型,泛型的方法
import java.util.ArrayList;
import java.util.List;
// =================【01:泛型接口 (Generic Interface)】=================
// 解析:T 是一个占位符,代表“某种类型”。接口定义了处理这种类型的标准。
interface IProcessor<T> {
void process(T item);
}
// =================【02:受限类型泛型类 (Bounded Type Class)】=================
// 解析:<T extends Number> 规定 T 必须是 Number 或其子类(如 Integer, Double)。
// 这样在类内部就可以安全地调用 Number 的方法,如 doubleValue()。
class Calculator<T extends Number> implements IProcessor<T> {
@Override
public void process(T item) {
// 因为有限制,编译器知道 item 一定有数字特征
System.out.println(">>> 正在处理数字: " + item.doubleValue());
}
// =================【03:泛型方法 (Generic Method)】=================
// 解析:这里的 <E> 与类的 <T> 无关。它允许该方法接收任何类型的参数。
// 即使类被限制为数字,这个方法依然可以处理 String。
public <E> void log(E info) {
System.out.println("[系统日志]: " + info.toString());
}
}
// =================【04 & 05:通配符 (Wildcards - PECS原则)】=================
class Warehouse {
// 【04:上界通配符 ? extends T】
// 解析:表示“某种 Number 的子类”。
// 场景:适合【读取】。因为不管是 Integer 还是 Double,它们都是 Number。
public static void readData(List<? extends Number> list) {
for (Number n : list) {
System.out.println("读取内容: " + n);
}
// list.add(10); // 禁止写入!编译器无法确定具体是哪种数字子类
}
// 【05:下界通配符 ? super T】
// 解析:表示“某种 Integer 的父类”。
// 场景:适合【写入】。因为 Integer 一定可以放入 Integer、Number 或 Object 的容器。
public static void writeData(List<? super Integer> list) {
list.add(999); // 允许写入 Integer
System.out.println("写入成功!");
}
}
public class GenericsHero {
public static void main(String[] args) {
// 实例化泛型类
Calculator<Double> myCalc = new Calculator<>();
myCalc.process(3.14);
myCalc.log("测试运行中..."); // 泛型方法调用
// 通配符测试
List<Integer> intList = new ArrayList<>();
intList.add(1);
Warehouse.readData(intList); // 向上转型读取
Warehouse.writeData(intList); // 向下转型写入
}
}
泛型方法为什么要单独写 <E>?
静态方法(static)无法访问类定义的泛型 T,因为静态成员属于类,而 T 只有在 new 对象时才确定。所以静态方法如果要用泛型,必须自己声明 <E>。
3,泛型的好处
| 特性 | 不用泛型 (使用 Object) | 使用泛型 |
| 类型安全 | 编译时不报错,运行时可能崩 (ClassCastException) | 编译时检查,写错类型代码直接报红 |
| 代码简洁 | 拿数据时必须手动强转:(String)box.get() |
自动识别类型,代码更干净 |
| 可读性 | 看到 List 不知道里面装的是什么 |
看到 List<User> 一眼就知道装的是用户 |
四,泛型错误用法
基本类型
List<int>✅ List<Integer>(泛型必须是对象类型)。
异常类
class MyException<T> extends Exception(异常不能带泛型)。
实例化
T obj = new T();(因为类型擦除,运行时 JVM 根本不知道 T 是什么,所以无法 new)。
更多推荐




所有评论(0)