Typesafe Swing事件 - “在运行时无法检查此类型测试中的外部引用”

时间:2013-05-08 20:53:44

标签: swing scala pattern-matching type-erasure

我正在实现一个Swing组件,我想要克服Reactor的####无类型。所以我认为这会奏效:

trait Foo[A] extends scala.swing.Publisher {
  final case class Bar(parent: Vector[A], children: A*) extends scala.swing.event.Event
}

trait Test {
  val foo: Foo[Int]

  foo.reactions += {
    case foo.Bar(parent, children) => {
      println(parent.sum - children)
    }
  }
}

不幸的是,这给了我两个编译器警告:

The outer reference in this type test cannot be checked at run time.
  final case class Bar(parent: Vector[A], children: A*) extends scala.swing.event.Event
                   ^
The outer reference in this type test cannot be checked at run time.
    case foo.Bar(parent, children) => {
                ^

我应该忽略这些警告吗?我可以抑制它们吗?我应该改变设计吗?

1 个答案:

答案 0 :(得分:30)

在Scala中,内部类是“路径依赖的”。

我将以您的代码为例。如果您有两个Foo[Int],称为foobar,则foo.Barbar.Bar的类型不同。请注意,这与Java的内部类的概念不同,foo.Barbar.Bar的类型相同。

在任何情况下,JVM都不直接支持内部类,因此在Java和Scala中,类Bar编译为名为Foo$Bar的JVM类。内部类几乎的实例始终包含对其所有者的引用 - “外部引用”。

现在,当你在路径依赖类型上有一个模式匹配时(比如代码中的那个),Scala编译器将生成字节码,它执行两件事:它将检查类(因此它将检查它收到的对象是Foo$Bar)的一个实例,它将检查外部引用(因此它将检查收到的对象的外部引用是foo)。

但是,在您的代码中,编译器无法找到检查外部引用的方法,因为您已将内部类声明为final。

因此,如果您忽略该警告,那么您的模式将匹配Foo$Bar的所有实例,即使它们不属于foo。你有一个比我更好的想法,这是否会成为一个问题。

或者,你可以通过让你的内部类不是最终的来解决它。

Ps,我不完全确定为什么Scala编译器无法检查最终内部类的外部引用,但我发现如果内部类是final,那么outer$是私有的,而如果这是非最终的,outer$是公开的。在编译器的内部可能值得一试,找出原因。

更新

事实证明这是一个已知问题 - SI-4440。如果不使用Scala编译器,则删除最终内部类的外部引用(这是非常合法的,因为子类也不可能使用它们)。这样做的一个积极结果是外部类可以被垃圾收集,而内部类仍在使用中,因此Scala开发人员不愿意重新引入外部引用,以免引入内存泄漏。