本章主要对Java泛型使用的8个限制进行说明。
1.Java泛型不能使用基本类型
java的基本类型:boolean,char,float,double,byte,int,short,long。
使用基本类型的泛型会编译报错,代码如下:
List<int> list = new List<int>();// 编译前类型检查报错
分析:因为泛型在编译时,会进行类型擦除,最后只保留原始类型。而原始类型只能是Object类及其子类,当然不能使用基本数据类型。
2.Java泛型不允许进行实例化
错误代码如下:
<T> void test(T t){
t = new T();//编译前类型检查报错
}
通过类型擦除,上面的泛型方法会转换为如下的原始方法:
void test(Object t){
t = new Object();
}
而,实际使用中,实参t一般情况下都是Object的子类,此时t = new Object();
相当于:
Integer t = 1;
t = new Object();
而Object肯定无法转换成Integer的。Java泛型为了防止此类类型转换错误的发生,禁止进行泛型实例化。
当然,与类型擦除一样,可以通过Java反射实现泛型的实例化,但是不建议这么做。这里就不给出代码了,有兴趣的可以自己尝试。
3.Java泛型不允许进行静态化
参考下面的代码:
/**
* <p>Title: 3.Java泛型不允许进行静态化</p>
* @author 韩超 2018/2/23 11:25
*/
static class Demo<T>{
private static T t;// 编译前类型检查报错
// 编译前类型检查报错
public static T getT() {
return t;
}
}
原因:静态变量在类中共享,而泛型类型是不确定的,因此编译器无法确定要使用的类型,所以不允许进行静态化。
备注:这里的静态化针对的对象是泛型变量,并不是泛型方法。感谢@ZYC_July的意见。
4.Java泛型不允许直接进行类型转换(通配符可以)
示例代码如下:
List<Integer> integerList = new ArrayList<Integer>();
List<Double> doubleList = new ArrayList<Double>();
//不能直接进行类型转换,类型检查报错
//integerList = doubleList;
我们已知,在编译阶段,两者都会经过类型擦除变为ArrayList,那为什么不能进行类型强制转换呢?
因为这违背了Java泛型设计的初衷:降低类型转换的安全隐患。
integerList内存储的是Integer类型的元素,doubleList 是存储的是Double类型的元素,如果不限制类型转换,很容易产生ClassCastException异常。
Java泛型不允许直接进行类型转换,但是通过通配符可以实现类型转换(不建议这么做)。
代码如下:
static void cast(List<?> orgin, List<?> dest){
dest = orgin;
}
public static void main(String[] args){
List<Integer> integerList = new ArrayList<Integer>();
List<Double> doubleList = new ArrayList<Double>();
//通过通配符进行类型转换
cast(doubleList,integerList);
}
关于通配符的相关知识,会在下一章节解析学习。
5.Java泛型不允许直接使用instanceof运算符进行运行时类型检查(通配符可以)
直接使用instanceof运算符进行运行时类型检查:
List<String> stringList = new ArrayList<String >();
//不能直接使用instanceof,类型检查报错
//LOGGER.info(stringList instanceof ArrayList<Double>);
我们可以通过通配符的方式进行instanceof运行期检查(不建议):
//通过通配符实现运行时验证
LOGGER.info(stringList instanceof ArrayList<?>);
result:
2018-02-23 14:33:20 INFO GenericAttentionDemo:71 - true
6.Java泛型不允许创建确切类型的泛型数组(通配符可以)
创建确切类型的泛型数组如下:
//类型检查报错
//Demo6<Integer>[] iDemo6s = new Demo6<Integer>[2];
我们可以通过通配符的方式创建泛型数组(不建议):
//通过通配符实现泛型数组
Demo6<?>[] iDemo6s = new Demo6<?>[2];
iDemo6s[0] = new Demo6<Integer>(1);
iDemo6s[1] = new Demo6<String >("hello");
for (Demo6 demo6 : iDemo6s){
LOGGER.info(demo6.getT().getClass().toString() + " : " + demo6.getT());
}
reuslt:
2018-02-23 14:58:01 INFO GenericAttentionDemo:97 - class java.lang.Integer : 1
2018-02-23 14:58:01 INFO GenericAttentionDemo:97 - class java.lang.String : hello
7.Java泛型不允许定义泛型异常类或者catch异常(throws可以)
直接看代码:
/**
* <p>Title: 7.Java泛型不允许定义泛型异常类</p>
*
* @author 韩超 2018/2/23 15:07
*/
// static class Apple<T> extends Exception {
// }
// static class Orange<T> extends Throwable {
// }
/**
* <p>Title: 7.Java泛型不允许捕获一个泛型异常</p>
*
* @author 韩超 2018/2/23 15:09
*/
//7.Java泛型:可以throws泛型类型
<T extends Exception> void test7(T t,Class<T> tClass) throws T {
try {
//...
// } catch (T t) {//不允许捕获一个泛型类型的异常
} catch (Exception e) {//不允许捕获一个泛型类型的异常
//...
}
}
为什么Java泛型不允许定义泛型异常类?为什么不允许捕获一个泛型异常?先看下面的代码:
//假设可以定义泛型异常类
try{
//。。。
}catch(MyException<Integer> e){
//。。。
}catch(MyException<Double> e){
//。。。
}
上面的代码,进行类型擦除时会去掉Integer和Double信息,形成如下代码:
try{
//。。。
}catch(MyException e){
//。。。
}catch(MyException e){
//。。。
}
同时catch两个一样的异常,肯定不能通过编译。
Java泛型为了避免这种错误的产生,不允许定义泛型异常类或者catch异常。
总结:
- Java泛型:不允许定义泛型异常类(直接或间接扩展Throwable类)
- Java泛型:不允许捕获一个泛型异常
- Java泛型:可以以异常类作为边界
- Java泛型:可以throws泛型类型
8.Java泛型不允许作为参数进行重载
代码如下:
/**
* <p>Title: 8.Java泛型不允许作为参数进行重载</p>
* @author 韩超 2018/2/23 15:32
*/
static class Demo8<T>{
void test(List<Integer> list){}
//不允许作为参数列表进行重载
//void test(List<Double> list){}
}
为什么呢?
因为类型擦除之后,两个方法是一样的参数列表,这种情况无法重载。
9.总结
Java泛型其实还有其他的使用限制,这里不再赘述。
我们在理解Java泛型的使用限制时,应该首先用心理解下面两点:
- Java泛型的设计初衷:降低类型转换的安全隐患,而非为了实现任意化。
- Java泛型的实现原理:类型擦除。