根据Java Language Sepecification,第3版:
It is a compile-time error if a generic class is a direct or indirect subclass of
Throwable
.
我希望理解为什么做出这个决定。通用异常有什么问题?
(据我所知,泛型只是编译时的语法糖,无论如何它们都会在Object
文件中被翻译为.class
,因此有效地声明泛型类就好像一切在它是Object
。如果我错了,请纠正我。)
答案 0 :(得分:146)
正如商标所说,这些类型不可再生,这在以下情况下是一个问题:
try {
doSomeStuff();
} catch (SomeException<Integer> e) {
// ignore that
} catch (SomeException<String> e) {
crashAndBurn()
}
SomeException<Integer>
和SomeException<String>
都被删除为相同的类型,JVM无法区分异常实例,因此无法分辨哪个catch
块应该是执行。
答案 1 :(得分:13)
以下是如何使用异常的简单示例:
class IntegerExceptionTest {
public static void main(String[] args) {
try {
throw new IntegerException(42);
} catch (IntegerException e) {
assert e.getValue() == 42;
}
}
}
TRy语句的主体抛出具有给定值的异常,该值由catch子句捕获。
相反,禁止以下新异常的定义,因为它创建了参数化类型:
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
这种限制是明智的,因为几乎任何捕获此类异常的尝试都必须失败,因为该类型不可恢复。人们可能期望异常的典型用法如下:
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子句中的类型不可恢复。在撰写本文时,Sun编译器报告了一系列语法错误:
% javac ParametricExceptionTest.java
ParametricExceptionTest.java:5: <identifier> expected
} catch (ParametricException<Integer> e) {
^
ParametricExceptionTest.java:8: ')' expected
}
^
ParametricExceptionTest.java:9: '}' expected
}
^
3 errors
由于异常不能参数化,因此语法受到限制,因此必须使用类型 写为标识符,没有以下参数。
答案 2 :(得分:12)
这主要是因为它设计得很糟糕。
这个问题阻止了干净的抽象设计,例如,
public interface Repository<ID, E extends Entity<ID>> {
E getById(ID id) throws EntityNotFoundException<E, ID>;
}
一个catch子句因泛型而失败的事实并没有被证明是没有任何借口的。 编译器可以简单地禁止在catch子句中扩展Throwable或禁止泛型的具体泛型类型。
答案 3 :(得分:4)
在编译时检查泛型的类型正确性。然后在名为 type erasure 的过程中删除泛型类型信息。例如,List<Integer>
将转换为非泛型类型List
。
由于类型擦除,无法在运行时确定类型参数。
我们假设您可以像这样延长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");
}
由于类型擦除,运行时将不知道要执行哪个catch块。
因此,如果泛型类是Throwable的直接或间接子类,那么它是编译时错误。
答案 4 :(得分:2)
我希望这是因为没有办法保证参数化。请考虑以下代码:
try
{
doSomethingThatCanThrow();
}
catch (MyException<Foo> e)
{
// handle it
}
正如您所说,参数化只是语法糖。但是,编译器会尝试确保参数化在编译范围内对象的所有引用中保持一致。在异常的情况下,编译器无法保证仅从正在处理的作用域中抛出MyException。
答案 5 :(得分:0)
与问题无关太,但如果您真的想要一个扩展 inner class
的 Throwable
,您可以声明它 static
。这适用于 Throwable
与封闭类在逻辑上相关但与该封闭类的特定泛型类型无关的情况。通过声明它 static
,它不会绑定到封闭类的实例,因此问题消失了。
以下(诚然不是很好)示例说明了这一点:
/** A map for <String, V> pairs where the Vs must be strictly increasing */
public class IncreasingPairs<V extends Comparable<V>> {
private final Map<String, V> map;
public IncreasingPairs() {
map = new HashMap<>();
}
public void insertPair(String newKey, V value) {
// ensure new value is bigger than every value already in the map
for (String oldKey : map.keySet())
if (!(value.compareTo(map.get(oldKey)) > 0))
throw new InvalidPairException(newKey, oldKey);
map.put(newKey, value);
}
/** Thrown when an invalid Pair is inserted */
public static class InvalidPairException extends RuntimeException {
/** Constructs the Exception, independent of V! */
public InvalidPairException(String newKey, String oldKey) {
super(String.format("Value with key %s is not bigger than the value associated with existing key %s",
newKey, oldKey));
}
}
}
进一步阅读:docs.oracle.com