【问题标题】:How come I can define generic exception types in Scala?为什么我可以在 Scala 中定义通用异常类型?
【发布时间】:2013-07-30 01:57:50
【问题描述】:

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

【问题讨论】:

  • 显然Scala生成的字节码是有效的,否则没有Scala程序可以运行。
  • 我认为这并不明显。即使在 Scala 中,您通常也不定义通用异常类型。即使你这样做了,仅仅因为它适用于主流 JVM 并不意味着它根据规范是有效的。
  • 你考虑过类型擦除吗?无论您抛出一个泛型或非泛型类型的对象,字节码都没有区别。就 JVM 而言,泛型类型根本不存在。
  • 字节码中仍有一些信息表明这是一个泛型类(参见我的编辑)。
  • 我没有看到 throw。我声称:当你抛出时,它不会对字节码产生任何影响。你提到的额外信息只是为了支持反射工具,如 IDE、调试器或 javap,它们对 JVM 没有任何作用..

标签: java scala jvm


【解决方案1】:

简答:

  • 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 上仅存在原始类型。

仍然有关于定义类的类型参数的信息,但没有使用 is 的位置!独立于参数化异常的声明,这在 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 本身并不关心。

【讨论】:

  • 我猜“如果泛型类是 Throwable 的直接或间接子类,这是编译时错误”仅适用于类的 定义,而不适用使用,如throw new Foo&lt;String&gt;("test");?
  • true,但它错误地认为因此,throw new Foo&lt;String&gt;("test") 无论如何都不会发生,请参阅编辑。
【解决方案2】:

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

这是根据 JLS 和 JVM 规范明确定义还是只是 不小心工作了?

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

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

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

【讨论】:

    猜你喜欢
    • 2019-10-09
    • 2014-12-30
    • 2015-04-18
    • 1970-01-01
    • 1970-01-01
    • 2020-02-20
    • 2016-12-17
    • 1970-01-01
    • 2019-07-05
    相关资源
    最近更新 更多