Java泛型杂谈

本文详细解读Java泛型的原理、应用及其局限性,包括类型擦除、静态变量共享、异常处理与通配符的使用,并提供泛型使用的最佳实践与常见问题解答。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java泛型(Generics)是JDK5中引入的特性,允许在定义类和接口的时候使用类型参数(type parameter),声明的类型参数在Runtime时替换为真正的具体类型。
泛型的优点是可以帮助编译器更早的检查一些类型错误,解决一些运行时异常,但为了保证与旧版本的JDK兼容,Java泛型的实现存在着一些不够优雅的地方。

正确理解泛型首先要理解类型擦除(type erasure)。Java的泛型基本都是在编译器这一层工作的,在生成的Java字节码中式不包含泛型中的类型信息的,换言之,类型参数会在编译器编译的时候被去掉,这个过程称之为类型擦除。举例如代码中定义的List<Object>和List<String>在编译之后都会变成List,JVM只会看到List。但Java编译器会在编译过程中尽可能的发现出隐含可能出错的地方,但仍然无法尽善尽美。所以Java的类型擦除机制不像C++中模板机制实现泛型那么优雅。主要是由于向前兼容,Java泛型支持更多的发生在编译层,不需要对JVM做任何的修改来支持。

首先我们简单看一下,C++中模板机制的泛型应用如下:
template <class T> 
T& max(T& t1, T& t2) { 
	return t1> t2? t1 : t2; 
} 

既可以适用int max(int i,int j),又可以适用double max(double i,double j),在编译时产生相对应的类型(如例子中的int和double)函数,而不是会擦除掉任何类型参数,灵活性高。

相比之下,Java中泛型的一些奇特的地方如下:
- Java泛型没有自己的Class类,所以静态变量是被泛型类的所有实例所共享的。(MyClass<T>访问静态变量方法仍然是MyClass.staticVar)
- Java泛型的类型参数不能用在Java异常处理的catch语句中,因为异常处理是由JVM在运行时执行的,所以JVM无法区分Exception<String>和Exception<Integer>

Java泛型的类型擦除的过程大致是:首先找到替换的具体类型,一般是Object,如果指定了泛型上界则使用上界。把代码中的类型参数都替换成具体的类,同时去掉<>中的类型声明。
编译器为了确保类型的安全性,会禁止某些泛型的使用方式,例如以下代码:

public void inspect(List<Object> list) {    
    for (Object obj : list)       
        System.out.println(obj);
		
	//possible happened
	//list.add(1); 
}


public void test() {    
    List<String> strs = new ArrayList<String>();    
    inspect(strs); //Compile error
}

编译时会发生错误: 无法将inspect(java.util.List<java.lang.Object>) 应用于(java.util.List<java.lang.String>) inspect(strs)
由于无法保证在inspect中对于list只加入String类型的元素,如可能会加入Integer或者任何非String类型,所以编译器不允许将List<String>当成List<Object>来使用。Java编译器会尽可能早的检查可能存在的类型安全问题,给出编译错误。(若编译器无法做出判断,则给出警告信息)

在Java泛型中还可以适用通配符,如List<?>声明了List中包含的元素类型是未知的,通配符所代表的是一组类型,只是具体类型不确定。不确定的元素只能用Object来引用,所以如果把上面例子中的List<Object>替换成List<?>则编译可以通过,但如果加上list.add(1)那么编译器会再次给出编译错误:
找不到符号
符号: 方法 add(int)

一般在使用通配符的时候建议加个上界,如List<? extends Number>,具体的类型可以根据上界来看,List<Integer>、List<Double>都会是合法的。
在使用泛型的时候一些经验:
1. 在代码中避免泛型类和原始类型的混用。比如List<String>和List不应该共同使用。这样会产生一些编译器警告和潜在的运行时异常。当需要利用JDK 5之前开发的遗留代码,而不得不这么做时,也尽可能的隔离相关的代码。
2. 在使用带通配符的泛型类的时候,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。
3. 泛型类最好不要同数组一块使用。你只能创建new List<?>[10]这样的数组,无法创建new List<String>[10]这样的。这限制了数组的使用能力,而且会带来很多费解的问题。因此,当需要类似数组的功能时候,使用集合类即可。

4. 不要忽视编译器给出的警告信息。


参考文章:http://www.infoq.com/cn/articles/cf-java-generics

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值