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

时间:2013-07-29 10:15:38

标签: java scala jvm

在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
}

2 个答案:

答案 0 :(得分: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
}

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

由于类型擦除,throw子句仅引用Foo,并且这两个catch方法将成为异常表中的两个条目,它们都引用类Foo,这两个条目都是无效的反正不可能。因此,在Java语言中无法使用语法,只能捕获Foo

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

答案 1 :(得分:2)

以下是我们在讨论中分析的答案。

  

这是根据JLS和JVM规范很好地定义的,还是只是   碰巧意外工作?

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

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

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