问题1
倘若我们在 IDE 中写上类似 public class CustomException<T> extends Throwable 的代码,那么 IDE 会提示我们 Genergic class may not extends ‘java.lang.Throwable’。
原因
- 以下为错误代码
try {
// may throws exception
doSomeStuff();
} catch (SomeException<Integer> e) {
doA();
} catch (SomeException<String> e) {
doB();
}
- 假设当前我们有两个类 —— SomeException<Integer> 类和 SomeException<String> 类,它们都是继承自 Throwable 类的。
- 代码中的 doSomeStuff() 方法可能是抛出 SomeException<Integer> 异常或 SomeException<String> 异常.
- 我们针对不同的异常做出不同的逻辑操作。这样看似完全没有问题.
- 但是熟悉泛型的小伙伴都知道,还有一种叫做类型擦除机制的存在,何为类型擦除?此处不扩展了.
- 通俗点说:java 中不存在泛型代码,泛型代码是写给我们看的,编译器会将泛型代码转换成普通类代码。所以无论是 SomeException<Integer> 或者是 SomeException<String> 经过编译器的类型擦除后都将会变成 SomeException。
- 故上述代码是不可以运行的,因为当代码抛出异常时编译器是无法判断走哪个 catch 分支的,所以 java 为了避免这样的问题出现,故泛型类是无法继承自 Throwable 类的。
注意
- 泛型类无法继承自
Exception/Throwable,而泛型可以继承自Exception/Throwable。 - 泛型类 class Generic<T>{}
- 泛型 T,K,V,E.....
//Error 这是错误的不可以的,因为Generic是泛型类他继承Exception
class Generic<T> extends Exception{
}
//Success 这是可以的因为Generic虽然是泛型类但是他没有继承Exception,它只是对T进行限定类型变量为Exception本身或子类
class Generic<T extends Exception>{
}
问题2
- 以下是错误代码
//不能捕获泛型类对象
public <T extends Throwable> void doWork(T x){
try{
}catch(T x){
//do sth;
}
}
原因
- 还是类型擦除的问题,我们知道catch是可以捕获多个异常的,而且异常是在运行时异常发生时捕获的,泛型异常被擦除后,有可能和另一个捕获的同一个,如下代码
- 以下是错误代码:
// 如果调用这个方法的时候传入一个RuntimeException 实例,
// 那么就会出现同时捕获两个RuntimeException,而具体走哪个分支?JVM都蒙圈了
public <T extends Throwable> void doWork(T x){
try{
}catch(T x){
//do sth1;
}catch (RuntimeException e){
//do sth2;
}
}
解决
- 虽然我们不能去catch它,但我们可以通过下面的方法区throws它
public <T extends Throwable> void doWorkSuccess(T x) throws T{
try{
}catch(Throwable e){
throw x;
}
}
总结思考
其实这两个问题都是源于泛型的擦除机制导致的,也就是说Java的泛型是JDK1.5后加入的,为了兼容之前的版本,其实他是一种伪泛型机制,在编译器把.java文件编译成.class文件后,泛型被擦除,变为原生类型,最能检验的就是我们都知道Java的重载,如下代码:
//可以重载
public void setData(String data){}
public void setData(Integer data){}
//不可以重载,IDE会报错
public void setData2(ArrayList<String> data){}
public void setData2(ArrayList<Integer> data){}
因为setData2在类型擦除后,他们的参数都变成了ArrayList的原生类型也就是ArrayList,所以这两个方法不是重载方法,而是一模一样的两个方法。
- 在使用泛型的时候一定要注意泛型的类型擦除机制。