I'd always presumed that object
declarations in Scala would get compiled into final
classes, as they are implemented by effectively anonymous classes. Since final
classes are more easily optimized by a JVM than nonfinal classes, I'd think there are benefits and no costs to finality, so all object
implementations would be final.
But I must be missing something. By default, object
implementation classes are nonfinal. One must explicitly declare final object
to get a final
implementation class:
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
What is the purpose of a nonfinal object
? Why would one ever not want to mark an object
final?
Update Note that a similar question has been asked and answered (thanks Travis Brown!) One answer is authoritative but must be out of date, SLS version 2.9 claims that "final is redundant for object definitions", but obviously that's not true, as of Scala 2.11 final
clearly affects the bytecode generated for objects.
The other (accepted!) answer points to the existence of a rarely-used feature whereby users can override an object in a subclass or trait. I think this is correct, and so my question here really is a duplicate, but it took me a few minutes to persuade myself of that. At first glance, overriding an object looks like overriding a val, in which case there'd be no reason why each implementation couldn't be marked final in bytecode. But on some more thought and a bit of searching, it must be true that if I have
trait Base {
object Poop {
def stink = "Yuk!"
}
def poopSmell = this.Poop.stink
}
trait Derived extends Base {
override object Poop {
def stink = "Roses"
}
}
then Derived's Poop must inherit the type of Base's Poop, so the implementation of Derived.Poop must be a subclass of the implementation of Base's Poop, so Base's poop cannot be marked final in bytecode.
So I think that this is a duplicate, though it took me a few minutes to work things out.
Note that this feature seems not to actually work very well, though. To get the above code to compile, one must run scala -Yoverride-objects
(or presumably scalac -Yoverride-objects
in a non-REPL context). Then the code above does compile in a REPL, but attempts to instantiate a refinement or concrete extension of Derived fail.
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)
Regardless of how broken this feature is, its existence does explain why it might sometimes (very rarely I think in practice) be desirable to have a nonfinal object
, so the prior accepted answer is correct and this is a duplicate.
I'm going to get into the habit of marking my object
s final almost always, though.
Update 2 Note, importantly, that marking an object final does not affect the lazy initialization semantics of the object:
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 Note, per som-snytt below, that object overriding could only ever apply to instance-dependent types, that is object
s nested (directly or indirectly) in classes or traits. Top-level objects cannot be overridden, so nor could "statically reachable" objects nested within top-level objects without any intervening classes or traits.
As som-snytt demonstrates in an answer below, top-level objects are in fact compiled into final classes.
Unfortunately, it's easy to show that statically reachable objects nested within top-level object do get compiled by default into nonfinal classes, so it is best to keep up a habit of declaring all but top-level objects final, even objects embedded in top-level objects, unless they are nested in classes or traits and you actually mean to enable object overriding. See the (edited) REPL session below, demonstrating both the finality of top-level objects (thanks again som-snytt!) and the non-finality of non-overridable object declarations nested within object declarations, unless the nested declarations are explicitly declared 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$();
}