我一直以为object
Scala 中的声明将被编译为final
类,因为它们是由有效的匿名类实现的。自从final
与非最终类相比,类更容易被 JVM 优化,我认为最终性有好处并且没有成本,所以所有object
实施将是最终的。
但我一定错过了一些东西。默认情况下,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
?为什么一个人会not想要标记一个object
final?
Update请注意,一个类似的问题 https://stackoverflow.com/questions/26079591/whats-the-point-of-declaring-an-object-as-final已被询问并得到答复(感谢特拉维斯·布朗!)一个答案是权威的,但必须是过时的,SLS版本2.9 http://www.scala-lang.org/docu/files/ScalaReference.pdf声称“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
(或者大概scalac -Yoverride-objects
在非 REPL 上下文中)。然后上面的代码确实在 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
,所以之前接受的答案是正确的,这是重复的。
我要养成标记我的习惯object
不过,几乎总是决赛。
Update 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
Update 3请注意,根据下面的 som-snytt,对象重写只能应用于依赖于实例的类型,即object
嵌套(直接或间接)在类或特征中。顶级对象不能被覆盖,因此,在没有任何干预类或特征的情况下,嵌套在顶级对象中的“静态可访问”对象也不能被覆盖。
As 索姆-斯尼特在下面的答案中演示了,顶级对象实际上被编译成最终类。
不幸的是,很容易表明嵌套在顶级对象中的静态可访问对象默认情况下会编译为非最终类,因此最好保持将除顶级对象之外的所有对象都声明为最终的习惯,甚至是嵌入在顶级对象中的对象。级别对象,除非它们嵌套在类或特征中并且您实际上打算启用对象覆盖。请参阅下面的(编辑过的)REPL 会话,演示了顶级对象的最终性(再次感谢索姆-斯尼特!) 以及嵌套在对象声明中的不可重写对象声明的非最终性,除非嵌套声明被显式声明为最终的。
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$();
}