Short answer:
As far as I can tell, values ββare always accessible through an access method. Using def defines a simple method that returns a value. Using val defines the final private [*] field using the access method. Thus, in terms of access, there are very few differences between them. The difference is conceptual, def reviewed every time, and val is evaluated only once. This can obviously affect performance.
[*] Java private
Long answer:
Take the following example:
trait ResourceDef { def id: String = "5" } trait ResourceVal { val id: String = "5" }
ResourceDef and ResourceVal create the same code, ignoring initializers:
public interface ResourceVal extends ScalaObject { volatile void foo$ResourceVal$_setter_$id_$eq(String s); String id(); } public interface ResourceDef extends ScalaObject { String id(); }
For helper classes created (which contain the implementation of methods), ResourceDef produces, as one would expect, noting that the method is static:
public abstract class ResourceDef$class { public static String id(ResourceDef $this) { return "5"; } public static void $init$(ResourceDef resourcedef) {} }
and for val we just call the initializer in the containing class
public abstract class ResourceVal$class { public static void $init$(ResourceVal $this) { $this.foo$ResourceVal$_setter_$id_$eq("5"); } }
When we start to expand:
class ResourceDefClass extends ResourceDef { override def id: String = "6" } class ResourceValClass extends ResourceVal { override val id: String = "6" def foobar() = id } class ResourceNoneClass extends ResourceDef
Where we override, we get a method in the class that just does what you expect. Def is a simple method:
public class ResourceDefClass implements ResourceDef, ScalaObject { public String id() { return "6"; } }
and val defines the private field and access method:
public class ResourceValClass implements ResourceVal, ScalaObject { public String id() { return id; } private final String id = "6"; public String foobar() { return id(); } }
Note that even foobar() does not use the id field, but uses an access method.
And finally, if we do not override, then we will get a method that calls the static method in the auxiliary feature class:
public class ResourceNoneClass implements ResourceDef, ScalaObject { public volatile String id() { return ResourceDef$class.id(this); } }
I cut out the constructors in these examples.
So, the access method is always used. I suggest that this is to avoid complications when expanding several features that can implement the same methods. This gets complicated very quickly.
Even longer answer:
Josh Sueret made a very interesting talk about Binary Resilience in Scala Days 2012 , which covers the background of this issue. Summary for this:
This article focuses on binary compatibility on the JVM, and what it means to be binary. The Scala binary incompatibility scheme in Scala is described in detail with the following set of rules and recommendations that will help developers ensure their own library releases are binary and binary.
In particular, this conversation is as follows:
- Features and binary compatibility
- Java serialization and anonymous classes
- Hidden creations of lazy vals
- Developing code that is binary resilient