文章目录
144. Java 泛型 - 对泛型的限制
无法使用基元类型实例化泛型类型
请考虑以下参数化类型:
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
}
创建 Pair
对象时,不能用基元类型(如 int
、char
)替换类型参数 K
或 V
:
Pair<int, char> p = new Pair<>(8, 'a'); // 编译错误
只能使用对应的包装类,例如 Integer
和 Character
:
Pair<Integer, Character> p = new Pair<>(8, 'a');
Java 编译器会自动进行 自动装箱(autoboxing):
Pair<Integer, Character> p = new Pair<>(Integer.valueOf(8), Character.valueOf('a'));
提示:自动装箱的机制可以帮助在基元类型和包装类型之间自动转换。
无法创建类型参数的实例
由于 类型擦除(type erasure),不能直接创建泛型类型参数的实例。例如,以下代码会导致编译错误:
public static <E> void append(List<E> list) {
E elem = new E(); // 编译错误
list.add(elem);
}
解决方案:可以使用 反射(Reflection)创建实例:
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.getDeclaredConstructor().newInstance(); // OK
list.add(elem);
}
调用示例:
List<String> ls = new ArrayList<>();
append(ls, String.class);
无法声明类型参数的静态字段
由于 静态变量是类级别的,而类型参数是 实例级别的,因此不允许在静态变量中使用泛型类型参数:
public class MobileDevice<T> {
private static T os; // 编译错误
}
这样会导致所有 MobileDevice
实例共享相同的 os
变量,但 T
在不同实例中可能代表不同的类型,造成 类型冲突。
不能使用 instanceof
或强制类型转换与参数化类型
由于 类型擦除,泛型类型的参数信息在运行时 不可用,因此不能使用 instanceof
进行类型检查:
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) { // 编译错误
// ...
}
}
可以使用 无界通配符 ?
进行 instanceof
检查:
public static void rtti(List<?> list) {
if (list instanceof ArrayList<?>) { // OK
// ...
}
}
同样,不能进行 不安全的强制类型转换:
List<Integer> li = new ArrayList<>();
List<Number> ln = (List<Number>) li; // 编译错误
但如果泛型类型一致,转换是允许的:
List<String> l1 = new ArrayList<>();
ArrayList<String> l2 = (ArrayList<String>) l1; // OK
无法创建参数化类型的数组
不能创建 泛型数组,如下代码会导致编译错误:
List<Integer>[] arrayOfLists = new List<Integer>[2]; // 编译错误
原因是数组在运行时存储实际的对象类型,而 泛型的类型参数会被擦除,导致类型信息丢失。
示例问题:
Object[] strings = new String[2];
strings[0] = "hi"; // OK
strings[1] = 100; // ArrayStoreException 异常
如果泛型数组被允许,则可能会导致 运行时错误无法检测:
Object[] stringLists = new List<String>[2]; // 假设允许
stringLists[0] = new ArrayList<String>(); // OK
stringLists[1] = new ArrayList<Integer>(); // 无法检测错误
无法创建、捕获或抛出参数化类型的异常
泛型类不能 直接或间接 继承 Throwable
,例如:
class MathException<T> extends Exception { } // 编译错误
class QueueFullException<T> extends Throwable { } // 编译错误
同样,不能捕获 泛型参数化的异常:
public static <T extends Exception, J> void execute(List<J> jobs) {
try {
for (J job : jobs) {
// ...
}
} catch (T e) { // 编译错误
// ...
}
}
但是,可以在 throws
子句中使用泛型参数:
class Parser<T extends Exception> {
public void parse(File file) throws T { // OK
// ...
}
}
不能重载类型擦除后签名相同的方法
泛型方法在 类型擦除 后可能会有相同的 方法签名,导致 编译错误:
public class Example {
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { } // 编译错误
}
因为 类型擦除 后,方法签名都变成 print(Set)
,导致重复定义。
解决方案:可以 更改方法名称 或 使用不同的参数类型。
以上是 Java 泛型的一些限制和解决方案,希望对你有所帮助!🚀