6

在 Java 中,定义泛型异常类是非法的。编译器将拒绝编译以下内容:

public class Foo<T> extends Throwable {
    // whatever...
}

但是,这个 Scala 代码编译得很好:

class Foo[T](val foo: T) extends Throwable

更奇怪的是,只要我捕捉到原始Foo类型,我就可以在 Java 代码中使用这个 Scala 类:

public class Main {
    public static void main(String[] args) {
        try {
            throw new Foo<String>("test");
        }
        catch(Foo e) {
            System.out.println(e.foo());
        }
    }
}

这会编译、运行和打印“test”。

这是根据 JLS 和 JVM 规范明确定义的,还是只是偶然发生的?

Java 对泛型异常的限制是纯粹的语言限制,还是也适用于字节码(在这种情况下,Scala 编译器生成的字节码将无效)?

编辑:这是 Scala 类在反编译后的样子:

public class Foo<T> extends java.lang.Throwable {
  public T value();
    Code:
       0: aload_0
       1: getfield      #15                 // Field value:Ljava/lang/Object;
       4: areturn

  public Foo(T);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #15                 // Field value:Ljava/lang/Object;
       5: aload_0
       6: invokespecial #22                 // Method java/lang/Throwable."<init>":()V
       9: return
}
4

2 回答 2

6

简短的回答:

  • JVM 规范禁止抛出和捕获参数化异常,但不关心声明。它甚至没有禁止,只是没有办法在字节码中表示类型参数,所以这个问题没有实际意义。
  • JLS 禁止声明它们,因为无论如何您都无法使用它们。

长答案:

Java 语言规范说(第 8.1.2 节)关于声明这样的类:

如果泛型类是 Throwable 的直接或间接子类(第 11.1.1 节),则会出现编译时错误。

由于 Java 虚拟机的 catch 机制仅适用于非泛型类,因此需要此限制。

它说关于抛出异常(§14.18):

throw 语句中的表达式必须表示

1) 可分配给 Throwable 类型(第 5.2 节)的引用类型的变量或值,或

2)空引用,或发生编译时错误。

Expression 的引用类型将始终是未参数化的类类型(因为没有接口类型可分配给 Throwable)(因为 Throwable 的子类不能是通用的(§8.1.2))

这个限制是在将泛型添加到 Java 语言时添加的,因为它们没有添加到 JVM 本身:JVM 上只存在原始类型。

仍然有关于定义类的类型参数的信息,但没有使用在哪里!独立于参数化异常的声明,这在 JVM 级别是不可能的

try {
    throw new Foo<String>("test");
} catch(Foo<Int> e) {
  // int
} catch(Foo<String> e) {
  // string
}

异常捕获的实现方式是有一个异常表,指定要监视的字节码范围和要捕获的关联类。该类不能有类型参数,因为无法在字节码中描述它们(JVM 规范,§2.10 和 §3.12)。

由于类型擦除,throw 子句只引用Foo,而这两个 catch 方法将成为异常表中的两个条目,都引用 class Foo,这无论如何都是无用和不可能的。因此,Java 语言中的语法是不可能的,只能捕获Foo.

因此,能够声明参数化异常变得非常无用且具有潜在危险。因此它们在语言中被完全禁止,即使 JVM 本身并不关心。

于 2013-07-29T12:58:50.297 回答
2

这是我们迄今为止在讨论中得出的答案。

这是根据 JLS 和 JVM 规范明确定义的,还是只是偶然发生的?

它不必根据 Java 语言规范很好地定义,因为它是一种不同的语言。

JVM,OTOH,在抛出和捕获此类异常时没有问题,因为由于类型擦除,它对字节码没有影响。

然而,有趣的问题仍然存在,为什么 Java 一开始就禁止使用泛型类扩展 Throwable。

于 2013-07-29T11:58:00.287 回答