3

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 objects 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 objects 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$();
}
Community
  • 1
  • 1
Steve Waldman
  • 13,689
  • 1
  • 35
  • 45
  • @travis-brown can you please post a link to the question you think this duplicates? i found questions relating to the finality of vals within objects, but not related to the finality of the implementation class itself. – Steve Waldman May 15 '15 at 17:10
  • the link is above the question in the yellowish box – cmbaxter May 15 '15 at 17:12
  • oh thanks. i see that now. i'll edit the question a bit. – Steve Waldman May 15 '15 at 17:14
  • @SteveWaldman I forgot that I can close unilaterally in the `scala` tag—normally I'd think of the duplicate vote as a suggestion. Let me know if you're not satisfied with the linked question (and its accepted answer). – Travis Brown May 15 '15 at 17:15
  • @TravisBrown thanks. let me add a bit to the question, and let me know whether you think there's a bit more to think about. – Steve Waldman May 15 '15 at 17:17
  • @TravisBrown OK. It took me a while to experiment and write up why the prior accepted answer is in fact correct (see the pretty detailed update to the post), but I am persuaded, so you are very welcome to close this. – Steve Waldman May 15 '15 at 17:54

1 Answers1

3

Is that your final answer?

Since REPL definitions are wrapped, you want to paste -raw to compile as-pasted.

It looks like top-level Foo and Bar are final classes:

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

Also, the hypothetical overriding of member objects is a red herring or chimaera. Even if objects are really just lazy vals, it's not obvious what type relation obtains.

som-snytt
  • 39,429
  • 2
  • 47
  • 129
  • I don't think object overriding is a chimera at all. On the contrary, it's the only explanation so far offered for nonfinal object classes. Good catch, re top-level objects! But that just reinforces the case for object overriding being the motivation for nonfinal, non-"statically reachable" dependent objects. There's no meaningful way to override a top-level object, nor objects embedded in top-level objects, so their default finality is consistent with the object overriding explanation. – Steve Waldman May 15 '15 at 21:07
  • It's objects that are children of classes or traits that can conceivably be overridden, and by the substitutability principle, the overriding objects would have to be subclasses of the class that implements the parent object and conform to the parent object types. So, to support object overriding, instance-dependent objects would have to be nonfinal. I think that's the best explanation so far. Do you have an alternative suggestion for dependent object default nonfinality? I'm pretty persuaded it's to support (someday properly implemented) overriding. – Steve Waldman May 15 '15 at 21:11
  • Nevertheless, your observation re the default finality of top-level objects soothes me a bit. I thought the nonfinality I observed a very crappy tradeoff: Scala seemed to forgo lots of JVM optimizability to support an almost-never-used feature. But most objects are top level or statically reachable, so most objects should be helpfully final by default. Only instance-dependent objects now demand some thoughtfulness about whether to attach `final`. Thank you very much for pointing that out! – Steve Waldman May 15 '15 at 21:14
  • So, I finally got around to checking, and object declarations nested within top-level object declarations compile by default to non-final classes. Object overriding cannot be the explanation for that -- there would be no way to override top-level objects, at least not until package inheritance gets invented. I still suspect that object overriding is the reason for non-top-level object nonfinality, and that statically reachable nested objects are nonfinal as an oversight, compiler implementers just used nestedness as a heuristic for overridability. Thank you again, your answer really helped. – Steve Waldman May 18 '15 at 04:43
  • (oh, i've added a third verbose update to the already oververbose edited question explaining this in detail, and of course crediting you.) – Steve Waldman May 18 '15 at 04:44
  • @SteveWaldman I don't think you reported this possible oversight, or at least I couldn't find it? https://issues.scala-lang.org/browse/SI-9790 – Alexey Romanov May 23 '16 at 06:51
  • @AlexeyRomanov i didn't, i took `-Yoverride-objects` to be sufficient explanation for the behavior, and took to (explicitly, annoyingly) marking my nested objects as final. but i'm glad you've reported it, let's see what people have to say about whether a not-so-utilized feature is worth the trouble. – Steve Waldman May 24 '16 at 02:53
  • @AlexeyRomanov I thought JIT was optimistic about finality anyway, so performance may not change? I'm not expert. The other twist is I think they want to disallow overriding a member class, so if `Poop` is a class; then arguably you shouldn't override `Poop.type` either. It's just an analogy that object is like a lazy val. – som-snytt May 24 '16 at 21:28