【发布时间】:2015-05-15 17:06:33
【问题描述】:
我一直认为 Scala 中的 object 声明会被编译成 final 类,因为它们是由有效的匿名类实现的。由于final 类比非final 类更容易被JVM 优化,我认为finality 有好处且没有成本,所以所有object 实现都是final。
但我一定错过了什么。默认情况下,object 实现类是非最终的。必须明确声明final object 才能获得final 实现类:
Welcome to Scala version 2.11.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_31).
Type in expressions to have them evaluated.
Type :help for more information.
scala> object Nonfinal;
defined object Nonfinal
scala> final object Final;
defined object Final
scala> :javap Nonfinal
Size 518 bytes
MD5 checksum f27390a538ccc6e45764beaeb888478c
Compiled from "<console>"
public class Nonfinal$
minor version: 0
major version: 50
flags: ACC_PUBLIC, ACC_SUPER
// snip!
scala> :javap Final
Size 509 bytes
MD5 checksum 2db90a8bab027857524debbe5cf8ef29
Compiled from "<console>"
public final class Final$
minor version: 0
major version: 50
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
非最终的object 的目的是什么?为什么有人不想要标记object 决赛?
更新请注意,similar question 已被询问和回答(感谢 Travis Brown!)一个答案是权威的,但必须是过时的,SLS version 2.9 声称“final 对于对象来说是多余的定义”,但显然这不是真的,从 Scala 2.11 final 开始,显然会影响为对象生成的字节码。
另一个(已接受!)答案指出存在一个很少使用的功能,用户可以通过该功能覆盖子类或特征中的对象。我认为这是正确的,所以我的问题确实是重复的,但我花了几分钟才说服自己。乍一看,重写一个对象就像重写一个 val,在这种情况下,没有理由不能在字节码中将每个实现标记为 final。但是经过更多的思考和一些搜索,如果我有的话一定是真的
trait Base {
object Poop {
def stink = "Yuk!"
}
def poopSmell = this.Poop.stink
}
trait Derived extends Base {
override object Poop {
def stink = "Roses"
}
}
那么 Derived 的 Poop 必须继承 Base 的 Poop 的类型,所以 Derived.Poop 的实现必须是 Base 的 Poop 实现的子类,所以 Base 的 poop 不能在字节码中标记为 final。
所以我认为这是重复的,尽管我花了几分钟才解决问题。
但请注意,此功能实际上似乎并不能很好地工作。要编译上述代码,必须运行 scala -Yoverride-objects(或者可能是在非 REPL 上下文中运行 scalac -Yoverride-objects)。然后上面的代码确实在 REPL 中编译,但尝试实例化 Derived 的细化或具体扩展失败。
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait Base {
object Poop {
def stink = "Yuk!"
}
def poopSmell = this.Poop.stink
}
trait Derived extends Base {
override object Poop {
def stink = "Roses"
}
}
// Exiting paste mode, now interpreting.
defined trait Base
defined trait Derived
scala> (new Derived{}).poopSmell
java.lang.ClassFormatError: Duplicate method name&signature in class file $anon$1
at java.lang.ClassLoader.defineClass1(Native Method)
...
scala> class Concrete extends Derived;
defined class Concrete
scala> new Concrete
java.lang.ClassFormatError: Duplicate method name&signature in class file Concrete
at java.lang.ClassLoader.defineClass1(Native Method)
不管这个特性有多糟糕,它的存在确实解释了为什么有时(我认为在实践中很少)有一个非最终的object 是可取的,所以之前接受的答案是正确的,这是重复的。
不过,我会养成在我的objects 决赛中标记的习惯。
更新 2 请注意,重要的是,将对象标记为 final 不会影响对象的惰性初始化语义:
scala> object PrintNonFinal{ println("PrintNonFinal") }
defined object PrintNonFinal
scala> final object PrintFinal{ println("PrintFinal") }
defined object PrintFinal
scala> identity( PrintNonFinal )
PrintNonFinal
res3: PrintNonFinal.type = PrintNonFinal$@2038ae61
scala> identity( PrintFinal )
PrintFinal
res4: PrintFinal.type = PrintFinal$@3dd4520b
更新 3 请注意,根据下面的 som-snytt,对象覆盖只能应用于实例相关类型,即 objects 嵌套(直接或间接)在类或特征中。顶级对象不能被覆盖,因此嵌套在顶级对象中的“静态可达”对象也不能没有任何干预类或特征。
正如 som-snytt 在下面的答案中所展示的,顶级对象实际上已编译为最终类。
不幸的是,很容易证明嵌套在顶级对象中的静态可访问对象在默认情况下确实会编译为非最终类,因此最好保持将除顶级对象之外的所有对象声明为最终对象的习惯,甚至是嵌入的对象在顶级对象中,除非它们嵌套在类或特征中,并且您实际上是要启用对象覆盖。请参阅下面的(已编辑)REPL 会话,展示了顶级对象的最终确定性(再次感谢 som-snytt!)和嵌套在对象声明中的不可覆盖对象声明的非确定性,除非嵌套声明显式声明为 final。
Welcome to Scala version 2.11.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_31).
scala> :paste -raw
// Entering paste mode (ctrl-D to finish)
package Foo {
object Outer {
object Inner {
}
}
}
// Exiting paste mode, now interpreting.
scala> :javap -sysinfo Foo.Outer$
Size 343 bytes
MD5 checksum 5502ef3151c41ab5c20ada0f0d386288
Compiled from "<pastie>"
public final class Foo.Outer$ {
public static final Foo.Outer$ MODULE$;
public static {};
}
scala> :javap -sysinfo Foo.Outer$Inner$
Size 410 bytes
MD5 checksum fa4749f47d8f6b432841a4f9947831b1
Compiled from "<pastie>"
public class Foo.Outer$Inner$ {
public static final Foo.Outer$Inner$ MODULE$;
public static {};
public Foo.Outer$Inner$();
}
scala> :paste -raw
// Entering paste mode (ctrl-D to finish)
package bar {
object Outer {
final object Inner {
}
}
}
// Exiting paste mode, now interpreting.
scala> :javap -sysinfo bar.Outer$
Size 343 bytes
MD5 checksum 138a1e48b7ea4c2d6097c552c9cac440
Compiled from "<pastie>"
public final class bar.Outer$ {
public static final bar.Outer$ MODULE$;
public static {};
}
scala> :javap -sysinfo bar.Outer$Inner$
Size 410 bytes
MD5 checksum 96c11a97df2d9630369f8f1c97db7089
Compiled from "<pastie>"
public final class bar.Outer$Inner$ {
public static final bar.Outer$Inner$ MODULE$;
public static {};
public bar.Outer$Inner$();
}
【问题讨论】:
-
@travis-brown 您能否发布一个链接,指向您认为重复的问题?我发现了与对象内 val 的最终性有关的问题,但与实现类本身的最终性无关。
-
链接在黄色方框中的问题上方
-
哦,谢谢。我现在明白了。我会稍微修改一下问题。
-
@SteveWaldman 我忘了我可以在
scala标签中单方面关闭——通常我认为重复投票是一种建议。如果您对链接的问题(及其接受的答案)不满意,请告诉我。 -
@TravisBrown 谢谢。让我补充一点问题,让我知道您是否认为还有更多需要考虑的地方。
标签: scala