Java 泛型是 JDK 5 中引入的一项重要特性,它提供了编译时类型安全检测机制,允许在编译时检测到非法的类型。
1. 泛型基础
1.1 泛型的引入
泛型的主要目的是参数化类型,即在类、接口或方法中使用类型参数,使得代码可以处理多种数据类型,同时保持类型安全。
示例:泛型类
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
T
是类型参数,可以在实例化时指定具体类型(如Integer
,String
)。
1.2 泛型类型的实例化
使用泛型类时,需在创建对象时指定具体类型:
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String content = stringBox.getContent(); // 无需强制类型转换
1.3 泛型方法
泛型方法可以独立于类的泛型参数,通过在方法返回类型前声明类型参数:
public static <T> T getFirstElement(T[] array) {
if (array.length > 0) {
return array[0];
}
return null;
}
调用:
Integer[] numbers = {1, 2, 3};
Integer first = getFirstElement(numbers); // 自动推断 T 为 Integer
1.4 泛型接口
泛型接口的实现类可以是泛型类或具体类型:
public interface List<T> {
void add(T element);
T get(int index);
}
// 泛型实现类
public class ArrayList<T> implements List<T> { ... }
// 具体类型实现类
public class StringList implements List<String> { ... }
2. 类型通配符
2.1 无界通配符 (<?>
)
用于表示未知类型,通常用于读取元素:
public static void printList(List<?> list) {
for (Object element : list) {
System.out.println(element);
}
}
2.2 上界通配符 (<? extends T>
)
限制通配符类型必须是 T
或其子类:
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number n : list) {
sum += n.doubleValue();
}
return sum;
}
- 可读取
Number
类型,但不能添加元素(除null
外)。
2.3 下界通配符 (<? super T>
)
限制通配符类型必须是 T
或其父类:
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
- 可添加
Integer
类型元素,但读取时只能作为Object
。
3. 类型擦除
Java 泛型是通过类型擦除实现的,即在编译时移除所有泛型类型信息:
- 泛型类型参数会被替换为其限定类型(若无限定则为
Object
)。 - 示例:
Box<T>
擦除后变为Box<Object>
。
类型擦除的影响:
- 无法在运行时获取泛型类型信息(如
list.getClass()
返回ArrayList.class
)。 - 不能创建泛型数组(如
new T[10]
不允许,但可通过(T[]) new Object[10]
绕过)。
4. 泛型的限制
4.1 不能实例化类型参数
// 错误
public Box() {
this.content = new T(); // 编译错误
}
解决方法:通过反射或工厂模式。
4.2 静态成员不能使用类的类型参数
public class Box<T> {
private static T defaultValue; // 错误:静态变量不能引用类型参数
}
但静态泛型方法可以:
public static <T> T getDefault() { ... }
4.3 不能使用基本数据类型
必须使用包装类:
Box<int> intBox = new Box<>(); // 错误
Box<Integer> intBox = new Box<>(); // 正确
4.4 泛型类不能继承 Exception
// 错误
public class MyException<T> extends Exception { ... }
5. 泛型的高级应用
5.1 多重限定
类型参数可同时限定多个接口或类(类必须放在首位):
public class MyClass<T extends Number & Comparable<T>> { ... }
5.2 递归类型限定
用于定义依赖自身类型的泛型:
public interface Comparable<T> {
int compareTo(T other);
}
public class Person implements Comparable<Person> { ... }
5.3 泛型与反射
虽然类型擦除会移除泛型信息,但可通过反射获取泛型签名:
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
public class GenericReflection {
public static void main(String[] args) {
List<String> list = List.of("a", "b");
Type type = list.getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType pType = (ParameterizedType) type;
Type[] typeArgs = pType.getActualTypeArguments();
System.out.println(typeArgs[0]); // 输出:class java.lang.String
}
}
}
6. 最佳实践
- 使用有意义的类型参数名:如
T
(Type)、K
(Key)、V
(Value)、E
(Element)。 - 优先使用泛型方法:若功能不依赖类的泛型参数,应设计为泛型方法。
- 合理使用通配符:
- 只读操作使用
<? extends T>
。 - 只写操作使用
<? super T>
。 - 读写操作使用具体类型。
- 只读操作使用
- 避免原始类型:使用原始类型(如
List
而非List<String>
)会失去泛型的类型安全优势。
总结
Java 泛型通过参数化类型提供了编译时类型安全,避免了运行时的 ClassCastException
,并增强了代码的复用性。理解类型擦除、通配符和泛型限制是掌握泛型的关键。在实际开发中,泛型广泛应用于集合框架、反射 API 和设计模式中。