为什么我可以在Scala中定义通用异常类型?

Tob*_*ndt 6 java jvm scala

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

public class Foo<T> extends Throwable {
    // whatever...
}
Run Code Online (Sandbox Code Playgroud)

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

class Foo[T](val foo: T) extends Throwable
Run Code Online (Sandbox Code Playgroud)

即使是奇怪的,只要我捕获原始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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这编译,运行和打印"测试".

这是根据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
}
Run Code Online (Sandbox Code Playgroud)

gou*_*ama 6

简短回答:

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

答案很长:

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

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

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

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

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

1)引用类型的变量或值,可赋值(§5.2)类型为Throwable,或

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
}
Run Code Online (Sandbox Code Playgroud)

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

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

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