[泛型] 使用泛型的注意事项

本文深入探讨了Java中泛型使用的限制,包括不能使用原始数据类型实例化泛型类、不能创建类型参数实例、不能使用类型参数声明静态属性、不能对泛型类使用强制类型转换和instanceof等关键点。
在 Java 中有效地使用泛型,一定要注意以下限制:

[b][size=x-large]不能使用原始数据类型实例化泛型类
[/size][/b]
考虑下面的泛型类:

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'));


[b][size=x-large]不能创建类型参数实例[/size][/b]

不能创建类型参数的实例,下面这段代码会出现编译错误:

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);


[b][size=x-large]不能使用类型参数声明静态属性[/size][/b]

类的静态属性是类级别的变量,被所有非静态对象共享。因此,不能使用类型参数声明静态属性。

考虑下面的代码:

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 类型。因此,不允许声明静态的类型参数属性。

[b][size=x-large]不能对泛型类使用强制类型转换和 instanceof[/size][/b]

因为 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


[b][size=x-large]不能创建泛型类的数组[/size][/b]

你不能创建泛型类的数组,例如,下面的代码将不能通过编译:

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


[b][size=x-large]不能创建、捕获或抛出类型参数的对象[/size][/b]

泛型类不能直接或间接继承 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
// ...
}
}


[b][size=x-large]不允许仅仅是类型参数不同的情况下进行方法重载[/size][/b]

下面这种写法将会发生编译错误:

public class Example {
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { }
}


文章来源:[url]http://www.aptusource.org/2014/04/restrictions-on-generics/[/url]
<think>嗯,用户现在想了解Java使用时的注意事项。之前已经回答过通配符的问题,现在用户进一步深入,可能是在实际开发中遇到了相关的问题,或者想巩固这方面的知识。我需要结合之前提供的引用内容,特别是引用[1]中的方法和普通方法的区别,引用[3]中提到的创建对象的注意事项,以及引用[2]的兼容性问题。首先,用户的问题是关于注意事项,所以需要整理出几个关键点。比如类擦除带来的限制,不能用于静态上下文,通配符的使用场景,类安全,还有继承和通配符的问题。引用[1]里提到类和方法的区别,比如普通方法使用类声明的,而方法自己声明参数。这点需要注意,避免混淆。 然后,引用[3]中提到了在类中创建对象的问题,比如使用反射可能会有异常,需要处理,而且一般不建议在类内部创建实例,而是在外部创建。这可能涉及到类擦除导致运行时类信息丢失的问题,所以需要提醒用户注意实例化时的类安全。 还有类擦除的问题,比如instanceof和强制类转换的限制,数组的创建,这些都需要作为注意事项列出。例如,不能直接创建数组,而是通过通配符或者强制转换的方式。另外,用户之前的问题涉及通配符的协变和逆变,这里可能需要提到PECS原则,即生产者使用extends,消费者使用super,这也是使用通配符时的重要注意事项。还要注意边界情况,比如类不能继承Throwable,这样用户在使用自定义异常时可能会犯错,需要提醒。最后,确保引用标记正确,比如在提到类擦除时引用[2],提到方法时引用[1],创建对象的问题引用[3]。这样用户能明确每个注意事项的来源,增强可信度。 可能还需要检查是否有遗漏的注意事项,比如原始类使用导致警告,或者在静态方法中的限制,这些在引用[1]的代码示例中也有体现,比如静态方法不能使用类的参数,必须自己声明。总结下来,注意事项大概包括:类擦除的影响、实例化的限制、通配符的正确使用、类安全、继承与实现的约束、与数组的兼容性、原始类的避免、静态上下文的限制、异常类的使用限制,以及PECS原则。每个点都需要简明扼要地解释,并给出示例代码说明问题,同时标注对应的引用来源。</think>### Java使用注意事项 --- #### 一、类擦除的限制 1. **运行时类信息丢失** 在编译后会进行**类擦除**,所有参数会被替换为边界类(如未指定边界则替换为`Object`)。例如: ```java List<String> list1 = new ArrayList<>(); List<Integer> list2 = new ArrayList<>(); System.out.println(list1.getClass() == list2.getClass()); // 输出true[^2] ``` 2. **无法使用`instanceof`检查** ```java if (list instanceof List<String>) { ... } // 编译报错 ``` --- #### 二、实例化的限制 1. **不能实例化参数** 无法直接通过`new T()`创建对象: ```java public class Box<T> { public T create() { return new T(); // 编译报错 } } ``` **解决方案**:通过反射或工厂方法(需传递`Class<T>`参数)[^3]。 2. **数组的创建限制** 不能直接创建数组: ```java T[] arr = new T[10]; // 编译报错 ``` **替代方案**:使用通配符或强制转: ```java List<?>[] lists = new ArrayList<?>[5]; // 允许但需谨慎类检查 ``` --- #### 三、通配符的使用边界 1. **`<?>`与具体的区别** - `List<?>`可接收任意类的List,但**只能读取**(读取为`Object`类),无法写入(除`null`外)[^1] - `List<T>`需明确指定类参数,支持读写操作 2. **PECS原则** - **Producer Extends**(生产者用`<? extends T>`):仅用于数据读取 - **Consumer Super**(消费者用`<? super T>`):仅用于数据写入[^1] --- #### 四、类安全与继承 1. **类不可继承`Throwable`** 自定义异常类不能使用: ```java class MyException<T> extends Exception { ... } // 编译报错 ``` 2. **与继承的兼容性** `List<String>`和`List<Object>`**无继承关系**,需通过通配符实现协变/逆变: ```java List<? extends Object> list = new ArrayList<String>(); // 协变 ``` --- #### 五、其他关键注意事项 1. **避免使用原始类** 使用未指定参数的原始类会失去类检查: ```java List list = new ArrayList(); // 原始类(不推荐) list.add("abc"); Integer num = (Integer) list.get(0); // 运行时抛出ClassCastException ``` 2. **静态上下文的限制** 类的静态方法/属性不能使用类的参数: ```java class Test<T> { static T data; // 编译报错 public static <E> void func(E e) { ... } // 方法允许(需自己声明E) } ``` 3. **方法的重载风险** 类擦除可能导致方法签名冲突: ```java void print(List<String> list) { ... } void print(List<Integer> list) { ... } // 编译报错:重复方法 ``` --- #### 示例:安全的方法设计 ```java // 通过Class参数保证类安全 public <T> T createInstance(Class<T> clazz) throws Exception { return clazz.getConstructor().newInstance(); } // 使用示例 String s = createInstance(String.class); // 安全创建String对象[^3] ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值