[泛型] 使用泛型的注意事项
程序员文章站
2022-04-25 10:00:55
...
在 Java 中有效地使用泛型,一定要注意以下限制:
不能使用原始数据类型实例化泛型类
考虑下面的泛型类:
在创建 Pair 对象的时候,类型参数 K 和 V 不能是原始数据类型:
类型参数 K 和 V 只能使用非原始数据类型:
Java 编译器会像下面这样使用 Integer.valueOf(8) 自动封装 8,使用 Character('a') 自动封装 'a':
不能创建类型参数实例
不能创建类型参数的实例,下面这段代码会出现编译错误:
为了规避这个问题,你可以使用反射机制:
现在可以像下面代码这样来调用 append 方法:
不能使用类型参数声明静态属性
类的静态属性是类级别的变量,被所有非静态对象共享。因此,不能使用类型参数声明静态属性。
考虑下面的代码:
如果允许类型参数声明静态属性,那么下面的代码将会很混乱:
因为静态属性 os 同时被 phone, pager, 和 pc 共享,那么现在 os 的真实类型是什么?它不可能同时是 Smartphone, Pager, 和 TabletPC 类型。因此,不允许声明静态的类型参数属性。
不能对泛型类使用强制类型转换和 instanceof
因为 Java 编译器在编译期会对所有泛型进行类型清除操作,因此,在运行时不能确定使用的是哪个类型的类型参数。
假如传递给 rtti 方法的 list 参数中的值如下:
运行时不会跟踪类型参数,因此也无法区分 ArrayList<Integer> 和 ArrayList<String> 的不同。你能做的最多是使用无限制的通配符来确保类型是 ArrayList:
通常情况下,除非使用了无限制的通配符,否则泛型类不能进行类型强制转换:
但是,在有的时候,如果编译器能确认类型参数是正确的情况下,可以进行类型强制转换:
不能创建泛型类的数组
你不能创建泛型类的数组,例如,下面的代码将不能通过编译:
下面的代码演示了为什么会发生这个错误:
那么如果是泛型类 List 的数组,同样会有这个问题:
不能创建、捕获或抛出类型参数的对象
泛型类不能直接或间接继承 Throwable 类,例如,下面的代码不能通过编译:
方法中也不能捕获类型参数异常:
唯一能做的是,将类型参数放到方法的 throws 后面:
不允许仅仅是类型参数不同的情况下进行方法重载
下面这种写法将会发生编译错误:
文章来源:http://www.aptusource.org/2014/04/restrictions-on-generics/
不能使用原始数据类型实例化泛型类
考虑下面的泛型类:
class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } // ... }
在创建 Pair 对象的时候,类型参数 K 和 V 不能是原始数据类型:
Pair<int, char> p = new Pair<>(8, 'a'); // 编译错误
类型参数 K 和 V 只能使用非原始数据类型:
Pair<Integer, Character> p = new Pair<>(8, 'a');
Java 编译器会像下面这样使用 Integer.valueOf(8) 自动封装 8,使用 Character('a') 自动封装 'a':
Pair<Integer, Character> p = new Pair<>(Integer.valueOf(8), new Character('a'));
不能创建类型参数实例
不能创建类型参数的实例,下面这段代码会出现编译错误:
public static <E> void append(List<E> list) { E elem = new E(); // compile-time error list.add(elem); }
为了规避这个问题,你可以使用反射机制:
public static <E> void append(List<E> list, Class<E> cls) throws Exception { E elem = cls.newInstance(); // OK list.add(elem); }
现在可以像下面代码这样来调用 append 方法:
List<String> ls = new ArrayList<>(); append(ls, String.class);
不能使用类型参数声明静态属性
类的静态属性是类级别的变量,被所有非静态对象共享。因此,不能使用类型参数声明静态属性。
考虑下面的代码:
public class MobileDevice<T> { private static T os; // ... }
如果允许类型参数声明静态属性,那么下面的代码将会很混乱:
MobileDevice<Smartphone> phone = new MobileDevice<>(); MobileDevice<Pager> pager = new MobileDevice<>(); MobileDevice<TabletPC> pc = new MobileDevice<>();
因为静态属性 os 同时被 phone, pager, 和 pc 共享,那么现在 os 的真实类型是什么?它不可能同时是 Smartphone, Pager, 和 TabletPC 类型。因此,不允许声明静态的类型参数属性。
不能对泛型类使用强制类型转换和 instanceof
因为 Java 编译器在编译期会对所有泛型进行类型清除操作,因此,在运行时不能确定使用的是哪个类型的类型参数。
public static <E> void rtti(List<E> list) { if (list instanceof ArrayList<Integer>) { // 编译错误 // ... } }
假如传递给 rtti 方法的 list 参数中的值如下:
{ ArrayList<Integer>, ArrayList<String> LinkedList<Character>, ... }
运行时不会跟踪类型参数,因此也无法区分 ArrayList<Integer> 和 ArrayList<String> 的不同。你能做的最多是使用无限制的通配符来确保类型是 ArrayList:
public static void rtti(List<?> list) { if (list instanceof ArrayList<?>) { // OK; instanceof requires a reifiable type // ... } }
通常情况下,除非使用了无限制的通配符,否则泛型类不能进行类型强制转换:
List<Integer> li = new ArrayList<>(); List<Number> ln = (List<Number>) li; // 编译错误
但是,在有的时候,如果编译器能确认类型参数是正确的情况下,可以进行类型强制转换:
List<String> l1 = ...; 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; // An ArrayStoreException is thrown.
那么如果是泛型类 List 的数组,同样会有这个问题:
Object[] stringLists = new List<String>[]; // 编译错误,但是我们假装可以这么做 stringLists[0] = new ArrayList<String>(); // OK stringLists[1] = new ArrayList<Integer>(); // An ArrayStoreException should be thrown
不能创建、捕获或抛出类型参数的对象
泛型类不能直接或间接继承 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) { } }
文章来源:http://www.aptusource.org/2014/04/restrictions-on-generics/