val and object inside a scala class?
Asked Answered
S

7

34

What is the difference between declaring a field as val, lazy val and object inside a scala class, as in the following snippet:

class A

class B {
  val a1 = new A      { def foo = 1 }
  object a2 extends A { def foo = 1 }
  lazy val a3 = new A { def foo = 1 }
}
Skinflint answered 10/8, 2010 at 12:4 Comment(2)
It turns out that lazy val a3 = new A { def foo = 1 } should have also been added to the question.Skinflint
See also Scala - new vs object extendsAlanaalanah
D
23

In the former, any code included is executed as soon as class B is created. In the latter, however, until you actually use the object, it won't be instantiated.

You can see the difference here:

class A { println("Creating a new A") }
class B {
  val a1 = new A { println("a1"); def foo = 1 }
  object a2 extends A { println("a2"); def foo = 1 }
}

scala> val b = new B
Creating a new A
a1
b: B = B@1176e8a

scala> b.a2.foo
Creating a new A
a2
res0: Int = 1

There are also hidden differences in what the created .class files are named and such; and of course the two have different types.

Dorise answered 10/8, 2010 at 12:15 Comment(6)
So object works like lazy val. Are there any practical differences between the two?Skinflint
Inherently, no, not as far as I know. The bytecode for object is a bit more compact. I'm not sure whether that means that lazy val is written inefficiently, or whether object might be unsafe under certain threading conditions, or both.Dorise
Or not as far as I remembered at that moment. See Alex Boisvert's comment for a critical difference!Dorise
Ouch! These two differences remind me the C++ gotchas (an object is just like a val, except that it is lazy, and except that when it is the field of a class and it is inherited, it cannot be overriden. Ouch. Maybe we should just use lazy vals instead of objects!Coquillage
For what it's worth, lazy vals do seem to be preferred to object members in the Scala code I've read. If you see an object member in the wild, it's probably from pre-2.6 Scala, when lazy vals were introduced.Kagu
Practical difference: invoking a method defined in an object is more efficient than invoking one defined in an anonymous class.Luciferin
L
19

I'm not sure that aioobe recognized the significance of his answer, but the different types actually represent a critical difference between vals and objects. In particular, the val and lazy val have a structural type (e.g. A{def foo: Int}), while the object has a singleton type. As a result, calls to the foo method on the vals involve reflection, while calls to the foo method on the object do not:

class A

class B {
  val a1 = new A      { def foo = printStack }
  object a2 extends A { def foo = printStack }
  lazy val a3 = new A { def foo = printStack }

  def printStack() = 
     new Exception().getStackTrace take 3 foreach println
}

scala> val b = new B
b: B = B@5c750

scala> b.a1.foo   // the val
line124$object$$iw$$iw$B.printStack(<console>:12)
line124$object$$iw$$iw$B$$anon$1.foo(<console>:7)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

scala> b.a2.foo   // the object
line124$object$$iw$$iw$B.printStack(<console>:12)
line124$object$$iw$$iw$B$a2$.foo(<console>:8)
line128$object$$iw$$iw$.<init>(<console>:9)

scala> b.a3.foo   // the lazy val
line124$object$$iw$$iw$B.printStack(<console>:12)
line124$object$$iw$$iw$B$$anon$2.foo(<console>:9)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Luciferin answered 8/12, 2010 at 22:14 Comment(2)
Zoiks! I didn't know anonymous classes used reflection. I guess using them for the pimp-my-library pattern is best avoided then: better to define a named class. Anonymous classes are used extensively in scala-swing but speed's not critical there.Meddle
@Luigi Plinge Anonymous classes use reflection only for methods that are not defined in the parent class/interface. If A had defined foo, even abstractly, no reflection would have been needed.Luciferin
A
18

One major difference is that val's can be overriden while objects can't.

class C extends B {                           
  override val a1 = new A { def foo = 2 }     
  override object a2 extends A { def foo = 2 }
}

leads to:

<console>:9: error: overriding object a2 in class B of type object C.this.a2;
object a2 cannot be used here - classes and objects cannot be overridden
override object a2 extends A { def foo = 2 }
Angelo answered 10/8, 2010 at 14:20 Comment(0)
G
3

I suppose one difference is that a1 will be of one subtype of A while a2 will be of another subtype of A namely a2.type.

scala> class A
defined class A

scala> val a1 = new A {def foo = 1}
a1: A{def foo: Int} = $anon$1@a9db0e2

scala> object a2 extends A {def foo = 1}
defined module a2

scala> a1
res0: A{def foo: Int} = $anon$1@a9db0e2

scala> a2
res1: a2.type = a2$@5b25d568

scala> 
Grajeda answered 10/8, 2010 at 12:7 Comment(7)
Not exactly. Both will be subclasses of A.Skinflint
Sure, but they will not be of the same type.Grajeda
To be more precise a2 will have a type a2.type.Airsick
Notice that your example Scala output contradicts what you say in the first sentence of your answer: the type of a1 is not A; the type is the type of the anonymous subclass you created. You can make it an A by explicitly specifying the type: val a1: A = new A {def foo=1}Postmaster
@Jesper: in this specific example, yes. He did raise a valid point, however. Using an object always creates a new type, whereas using a val does not (unless you add behavior as in this case).Luciferin
@Jesper: Just noticed this. The type of a1 is not the type of the anonymous subclass; it's the structural type A{def foo: Int}, which differs from the anonymous subclass in that invoking foo will use reflection. You can verify this by having foo print a stack trace.Luciferin
@Aaron: Yes, that's right, and because it's going to be called using reflection, the call will be much slower than a regular method call.Postmaster
S
3

Another major difference is that objects know their own name and val's do not.

Scheffler answered 13/1, 2012 at 13:59 Comment(0)
C
2

The first practical difference is that lazy vals and objects are lazy, whereas vals are eager.

The main difference between objects and lazy vals is that an object is, from the languages perspective considered to be a "singleton," which from the jvm's perspective is generally treated as a static member. The object definition in the given example cannot be overriden, as others have demonstrated, for the same reason static members cannot be overriden: without being tied to an instance, there's no conceivable way to do a virtual function lookup.

object Foo { object Bar extends A; }

is loosely like the following java code:

class Foo { 
  private static class Bar extends A{}
  public static Bar Bar = new Bar;
}

If in the above example, if a subclass C extends Foo was defined, it would not be able to override the definition of Bar. The static instance Bar in Java would be accessed as Foo.Bar. C.Bar does not mean the same thing as (new C).Bar. I might be a bit off here, I haven't actually tried de-compiling scala code, this is just an example to illustrate the general concept of objects as static members.

lazy vals can be a bit less efficient. Last time I checked, they were implemented by maintaining a hidden field in the class that kept track of which lazy vals had been initialized. Maintaining this field requires locking, which can cause performance issues.

One major practical difference between lazy val and object is treatment of failure:

If I have:

class Foo() { throw new Exception("blah!"); }
object Stuff { object Bar extends Foo { val x = "hi" } }
Stuff.Bar
//exception "blah!" thrown.
Stuff.Bar.x
//NoClassDefFoundError: Could not initialize Stuff$Bar$

whereas if I do:

object Stuff2 { lazy val Bar = new Foo() { val x = "hi" } }
Stuff2.Bar
// "blah!"
Stuff2.Bar.x
// "blah!"

The "NoClassDefFoundError" can be really confusing, and since it's an Error not an Exception it can break error handling code that (appropriately) catches/logs "Exception" but allows errors to propagate. I might even consider this sort of a bug in the Scala language, since this use-case does in fact indicate an exceptional condition, not truly a JVM error. I've seen NoClassDefFoundErrors when accessing objects that depended on external resources (e.g. database connections or files on disk). Only the first access logs the underlying cause, so proper debugging of such an issue typically requires restarting your application server.

Choral answered 1/4, 2014 at 18:0 Comment(0)
B
0

This is not a structural type: val a = new A { def foo = 1 }

It creates a unique anonymous subclass; a.foo invokes foo in that class.

x here is a structural type: def bar( x: { def bass: Int} )

x.bass will introspect x (of unknown type) to find a method with name 'bass'. It will work with fish or musical instruments. ;)

One difference between the lazy val and object is this:

var someA = (new B).a3
someA = (new B).a3 // ok

var anotherA = (new B).a2
anotherA =  = (new B).a2 // compile error
Brio answered 1/11, 2011 at 16:29 Comment(1)
The reason you get a compile error is because you have two equals signs one after the otherMeddle

© 2022 - 2024 — McMap. All rights reserved.