scala中非最终单例对象的重点是什么?

时间:2015-05-15 17:06:33

标签: scala

我总是假设Scala中的object声明会被编译成final类,因为它们是由有效的匿名类实现的。由于JVM比非最终类更容易优化final类,因此我认为最终会有好处而且没有成本,因此所有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的目的是什么?为什么想要标记object决赛?

更新请注意similar question已被问及并回答(感谢Travis Brown!)一个答案是权威的,但必须过时,SLS version 2.9声称“最终对于对象定义是多余的“,但显然不是这样,因为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。

所以我认为这是重复的,虽然我花了几分钟时间来解决问题。

请注意,此功能似乎并没有真正起作用。要获得上面的代码进行编译,必须在非REPL上下文中运行scala -Yoverride-objects(或者大概是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,所以先前接受的答案是正确的,这是一个重复。

我会养成几乎总是标记object决赛的习惯。

更新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,对象覆盖只能应用于依赖于实例的类型,即object嵌套(直接或间接)在类中或特征。无法覆盖顶级对象,因此也不能将“静态可达”对象嵌套在顶级对象中,而不会有任何插入的类或特征。

正如 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$();
}

1 个答案:

答案 0 :(得分:2)

这是你的最终答案吗?

由于包装了REPL定义,您希望paste -raw编译为已粘贴。

看起来顶级Foo和Bar是最终类:

scala> :pa -raw
// Entering paste mode (ctrl-D to finish)

package foo { object Foo { object Fooz } }
package bar { final object Bar { object Baz} }

// Exiting paste mode, now interpreting.


scala> :javap -sysinfo foo.Foo$
  Size 339 bytes
  MD5 checksum 628ac559ca72294bd342a7813ced5d4c
  Compiled from "<pastie>"
public final class foo.Foo$ {
  public static final foo.Foo$ MODULE$;
  public static {};
}

scala> :javap -sysinfo bar.Bar$
  Size 339 bytes
  MD5 checksum 23a123e9827f1a0a6b033253e0613545
  Compiled from "<pastie>"
public final class bar.Bar$ {
  public static final bar.Bar$ MODULE$;
  public static {};
}

scala> :javap -sysinfo foo.Foo$Fooz$
  Size 401 bytes
  MD5 checksum a081412707f1fbf42f6fa68f2c7f46e9
  Compiled from "<pastie>"
public class foo.Foo$Fooz$ {
  public static final foo.Foo$Fooz$ MODULE$;
  public static {};
  public foo.Foo$Fooz$();
}

此外,成员对象的假设覆盖是红鲱鱼或chimaera。即使对象实际上只是惰性值,也不清楚类型关系是什么。