为什么Java不允许Throwable的泛型子类?

时间:2009-02-01 18:07:52

标签: java generics exception language-design

根据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。如果我错了,请纠正我。)

6 个答案:

答案 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的直接或间接子类,那么它是编译时错误。

来源: Problems with type erasure

答案 4 :(得分:2)

我希望这是因为没有办法保证参数化。请考虑以下代码:

try
{
    doSomethingThatCanThrow();
}
catch (MyException<Foo> e)
{
    // handle it
}

正如您所说,参数化只是语法糖。但是,编译器会尝试确保参数化在编译范围内对象的所有引用中保持一致。在异常的情况下,编译器无法保证仅从正在处理的作用域中抛出MyException。

答案 5 :(得分:0)

与问题无关,但如果您真的想要一个扩展 inner classThrowable,您可以声明它 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