一、什么是泛型
泛型(Generics)是 JDK 5.0 引入的新特性,它允许在定义类、接口或方法时使用类型参数。这种类型参数在使用时指定具体类型。
-
泛型是什么? → 类型的参数
-
为什么要用? → 编译时发现错误,不用运行时崩溃
-
<T>是什么? → 占位符,使用时替换成具体类型 -
<?>是什么? → 不知道具体类型,只能读不能写 -
<? extends T>→ 知道是T或其子类,可以安全读取为T -
<? super T>→ 知道是T或其父类,可以安全写入T
二、为什么需要泛型
1. 类型安全
java
// 没有泛型的问题
List list = new ArrayList();
list.add("hello");
list.add(123); // 编译通过,运行时可能出错
String s = (String) list.get(1); // 运行时 ClassCastException
java
// 使用泛型
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // 编译错误
String s = list.get(0); // 不需要强制类型转换
2. 消除强制类型转换
3. 提高代码复用性
三、泛型的基本使用
1. 泛型类
java
// 定义泛型类
public class Box<T> {
private T content;
public Box(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
}
// 使用泛型类
Box<String> stringBox = new Box<>("Hello");
Box<Integer> integerBox = new Box<>(123);
2. 泛型接口
java
// 定义泛型接口
public interface Repository<T> {
void save(T entity);
T findById(Long id);
}
// 实现泛型接口
public class UserRepository implements Repository<User> {
@Override
public void save(User entity) {
// 实现保存逻辑
}
@Override
public User findById(Long id) {
// 实现查询逻辑
return new User();
}
}
3. 泛型方法
java
public class GenericMethods {
// 泛型方法
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
// 静态泛型方法
public static <T> T getFirst(T[] array) {
return array[0];
}
}
// 使用泛型方法
GenericMethods gm = new GenericMethods();
String[] strings = {"a", "b", "c"};
gm.printArray(strings);
Integer[] numbers = {1, 2, 3};
Integer first = GenericMethods.getFirst(numbers);
四、类型参数命名约定
-
E- Element (集合中的元素) -
K- Key (键) -
V- Value (值) -
N- Number (数字) -
T- Type (类型) -
S,U,V- 第二、三、四个类型
五、类型通配符
1. 上界通配符 <? extends T>
java
public void processNumbers(List<? extends Number> list) {
// 可以读取,但不能添加(null除外)
for (Number n : list) {
System.out.println(n.doubleValue());
}
// list.add(123); // 编译错误
}
// 可以传入 List<Integer>, List<Double>, List<Number>
2. 下界通配符 <? super T>
java
public void addNumbers(List<? super Integer> list) {
// 可以添加 Integer 或其子类
list.add(123);
// list.add(new Object()); // 编译错误
// 读取时需要强制类型转换
Object obj = list.get(0);
}
// 可以传入 List<Integer>, List<Number>, List<Object>
3. 无界通配符 <?>
java
public void printList(List<?> list) {
// 只能使用 Object 的方法
for (Object obj : list) {
System.out.println(obj);
}
// list.add("hello"); // 编译错误
}
六、泛型限制
1. 类型擦除
Java 泛型是编译期的特性,运行时类型信息被擦除。
java
// 编译后,所有泛型信息都被擦除为原始类型
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
// 运行时 list1.getClass() == list2.getClass() 返回 true
2. 不能实例化泛型类型
java
public class Example<T> {
// 错误示例
private T instance = new T(); // 编译错误
// 正确做法:使用反射(需传递 Class 对象)
private Class<T> clazz;
public Example(Class<T> clazz) {
this.clazz = clazz;
}
public T createInstance() throws Exception {
return clazz.newInstance();
}
}
3. 不能使用基本类型
java
// 错误:不能使用基本类型
// List<int> list = new ArrayList<>(); // 编译错误
// 正确:使用包装类
List<Integer> list = new ArrayList<>();
4. 不能创建泛型数组
java
// 错误示例
// T[] array = new T[10]; // 编译错误
// 正确做法
public class ArrayExample<T> {
private T[] array;
@SuppressWarnings("unchecked")
public ArrayExample(Class<T> clazz, int size) {
array = (T[]) Array.newInstance(clazz, size);
}
}
七、实际应用示例
1. 泛型工具类
java
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
// getters and setters
}
2. 泛型与集合框架
java
// 安全的集合操作
Map<String, List<Student>> studentMap = new HashMap<>();
List<Student> mathStudents = new ArrayList<>();
mathStudents.add(new Student("张三", 20));
studentMap.put("数学", mathStudents);
// 类型安全的获取
List<Student> students = studentMap.get("数学");
3. 泛型与比较器
java
public class ComparatorExample {
// 通用比较器
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
}
八、最佳实践
-
使用有意义的类型参数名
-
尽量使用泛型方法而不是泛型类(当只有少数方法需要泛型时)
-
优先使用通配符增加 API 灵活性
-
避免使用原生类型
-
理解类型擦除的影响
九、Java 8+ 中的增强
1. 目标类型推断改进
java
// Java 7 需要指定类型
List<String> list = new ArrayList<String>();
// Java 8+ 可以使用菱形操作符
List<String> list = new ArrayList<>();
2. Lambda 表达式中的泛型
java
Function<String, Integer> stringToInt = Integer::parseInt;
Supplier<List<String>> listSupplier = ArrayList::new;
总结
Java 泛型提供了编译时类型安全,减少了运行时错误,提高了代码的可读性和重用性。虽然存在类型擦除等限制,但合理使用泛型可以显著提升代码质量。