我正在实现一个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) => {
^
我应该忽略这些警告吗?我可以抑制它们吗?我应该改变设计吗?
答案 0 :(得分:30)
在Scala中,内部类是“路径依赖的”。
我将以您的代码为例。如果您有两个Foo[Int]
,称为foo
和bar
,则foo.Bar
与bar.Bar
的类型不同。请注意,这与Java的内部类的概念不同,foo.Bar
和bar.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开发人员不愿意重新引入外部引用,以免引入内存泄漏。