PeWu
PeWu

Reputation: 641

val and object inside a scala class?

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 }
}

Upvotes: 34

Views: 10785

Answers (7)

nairbv
nairbv

Reputation: 4323

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.

Upvotes: 2

Aaron Novstrup
Aaron Novstrup

Reputation: 21017

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)

Upvotes: 19

Eric Pabst
Eric Pabst

Reputation: 546

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

Upvotes: 3

Mark
Mark

Reputation: 1

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

Upvotes: 0

aioobe
aioobe

Reputation: 421310

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> 

Upvotes: 3

Alex Boisvert
Alex Boisvert

Reputation: 2918

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 }

Upvotes: 18

Rex Kerr
Rex Kerr

Reputation: 167921

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.

Upvotes: 23

Related Questions