28

我正在实现一个 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) => {
                ^

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

4

1 回答 1

41

在 Scala 中,内部类是“路径相关的”。

我将以您的代码为例。如果你有两个Foo[Int]s,称为fooand bar,那么foo.Bar是与 不同的类型bar.Bar。请注意,这与 Java 的内部类概念不同,其中foo.Barbar.Bar是相同的类型。

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

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

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

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

或者,您可以通过使您的内部类成为非最终类来修复它。

Ps,我不完全确定为什么 Scala 编译器不能检查最终内部类的外部引用,但我发现如果内部类是最终的,那么outer$就是私有的,而如果它不是最终的,outer$就是公共的。可能值得在编译器的内部进行一番探讨,以找出原因。

更新

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

于 2013-05-09T16:26:13.937 回答