为什么Java不允许Throwable的通用子类?
根据Java语言select第3版:
如果generics类是
Throwable
的直接或间接子类,那么这是一个编译时错误。
我想了解为什么做出这个决定。 genericsexception有什么问题?
(据我所知,generics只是编译时语法糖,在.class
文件中它们会被翻译成Object
,所以有效地声明一个generics类就好像它的一切都是一个Object
。我错了。)
正如马克所说,这些types是不可确定的,这在以下情况下是一个问题:
try { doSomeStuff(); } catch (SomeException<Integer> e) { // ignore that } catch (SomeException<String> e) { crashAndBurn() }
SomeException<Integer>
和SomeException<String>
都被擦除为相同types,JVM无法区分exception实例,因此无法分辨哪个catch
块应该执行。
这里是一个简单的例子,说明如何使用exception:
class IntegerExceptionTest { public static void main(String[] args) { try { throw new IntegerException(42); } catch (IntegerException e) { assert e.getValue() == 42; } } }
TRy语句的主体抛出exception与一个给定的值,由catch子句捕获。
相比之下,新的exception的定义是禁止的,因为它创build了一个参数化的types:
class ParametricException<T> extends Exception { // compile-time error private final T value; public ParametricException(T value) { this.value = value; } public T getValue() { return value; } }
试图编译上述报告错误:
% javac ParametricException.java ParametricException.java:1: a generic class may not extend java.lang.Throwable class ParametricException<T> extends Exception { // compile-time error ^ 1 error
这种限制是明智的,因为几乎任何试图捕捉这种exception的尝试都必须失败,因为这种types是不可确定的。 人们可能会期望典型的例外使用如下所示:
class ParametricExceptionTest { public static void main(String[] args) { try { throw new ParametricException<Integer>(42); } catch (ParametricException<Integer> e) { // compile-time error assert e.getValue()==42; } } }
这是不允许的,因为catch子句中的types是不可确定的。 在撰写本文时,Sun编译器在这种情况下报告了一系列语法错误:
% javac ParametricExceptionTest.java ParametricExceptionTest.java:5: <identifier> expected } catch (ParametricException<Integer> e) { ^ ParametricExceptionTest.java:8: ')' expected } ^ ParametricExceptionTest.java:9: '}' expected } ^ 3 errors
由于exception不能是参数,所以语法是受限制的,因此必须将该types写为标识符,而没有以下参数。
这基本上是因为它的devise不好。
这个问题可以防止干净的抽象devise,
public interface Repository<ID, E extends Entity<ID>> { E getById(ID id) throws EntityNotFoundException<E, ID>; }
generics条款对generics不合格的事实没有被certificate是没有借口的。 编译器可以简单地禁止扩展Throwable的genericstypes,或者在catch子句中不允许使用generics。
我希望这是因为没有办法保证参数化。 考虑下面的代码:
try { doSomethingThatCanThrow(); } catch (MyException<Foo> e) { // handle it }
如你所说,参数化只是语法糖。 但是,编译器试图确保在编译范围内的对象的所有引用中参数化保持一致。 在发生exception的情况下,编译器无法保证MyException仅从正在处理的作用域中抛出。
generics在编译时检查types是否正确。 genericstypes信息然后在称为types擦除的进程中被删除 。 例如, List<Integer>
将被转换为非genericsList
。
由于types擦除 ,types参数不能在运行时确定。
让我们假设你可以像这样扩展Throwable
:
public class GenericException<T> extends Throwable
现在让我们考虑下面的代码:
try { throw new GenericException<Integer>(); } catch(GenericException<Integer> e) { System.err.println("Integer"); } catch(GenericException<String> e) { System.err.println("String"); }
由于types擦除 ,运行时将不知道要执行哪个catch块。
因此,如果generics类是Throwable的直接或间接子类,那么这是一个编译时错误。
来源: types删除的问题