Is there any advantage to definining a val over a

2019-03-15 08:07发布

In Scala, a val can override a def, but a def cannot override a val.

So, is there an advantage to declaring a trait e.g. like this:

trait Resource {
  val id: String
}

rather than this?

trait Resource {
  def id: String
}

The follow-up question is: how does the compiler treat calling vals and defs differently in practice and what kind of optimizations does it actually do with vals? The compiler insists on the fact that vals are stable — what does in mean in practice for the compiler? Suppose the subclass is actually implementing id with a val. Is there a penalty for having it specified as a def in the trait?

If my code itself does not require stability of the id member, can it be considered good practice to always use defs in these cases and to switch to vals only when a performance bottleneck has been identified here — however unlikely this may be?

4条回答
forever°为你锁心
2楼-- · 2019-03-15 08:41

Short answer:

As far as I can tell, the values are always accessed through the accessor method. Using def defines a simple method, which returns the value. Using val defines a private [*] final field, with an accessor method. So in terms of access, there is very little difference between the two. The difference is conceptual, def gets reevaluated each time, and val is only evaluated once. This can obviously have an impact on performance.

[*] Java private

Long answer:

Let's take the following example:

trait ResourceDef {
  def id: String = "5"
}

trait ResourceVal {
  val id: String = "5"
}

The ResourceDef & ResourceVal produce 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 the subsidiary classes produced (which contain the implementation of the methods), the ResourceDef produces is as you 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 the val, we simply call the initialiser 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 extending:

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 which just does what you expect. The def is simple method:

public class ResourceDefClass implements ResourceDef, ScalaObject {
    public String id() {
        return "6";
    }
}

and the val defines a private field and accessor 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() doesn't use the field id, but uses the accessor method.

And finally, if we don't override, then we get a method which calls the static method in the trait auxiliary class:

public class ResourceNoneClass implements ResourceDef, ScalaObject {
    public volatile String id() {
        return ResourceDef$class.id(this);
    }
}

I've cut out the constructors in these examples.

So, the accessor method is always used. I assume this is to avoid complications when extending multiple traits which could implement the same methods. It gets complicated really quickly.

Even longer answer:

Josh Suereth did a very interesting talk on Binary Resilience at Scala Days 2012, which covers the background to this question. The abstract for this is:

This talk focuses on binary compatibility on the JVM and what it means to be binary compatible. An outline of the machinations of binary incompatibility in Scala are described in depth, followed by a set of rules and guidelines that will help developers ensure their own library releases are both binary compatible and binary resilient.

In particular, this talk looks at:

  • Traits and binary compatibility
  • Java Serialization and anonymous classes
  • The hidden creations of lazy vals
  • Developing code that is binary resilient
查看更多
爱情/是我丢掉的垃圾
3楼-- · 2019-03-15 08:53

A val expression is evaluated once on variable declaration, it is strict and immutable.

A def is re-evaluated each time you call it

查看更多
The star\"
4楼-- · 2019-03-15 09:00

The difference is mainly that you can implement/override a def with a val but not the other way around. Moreover val are evaluated only once and def are evaluated every time they are used, using def in the abstract definition will give the code who mixes the trait more freedom about how to handle and/or optimize the implementation. So my point is use defs whenever there isn't a clear good reason to force a val.

查看更多
可以哭但决不认输i
5楼-- · 2019-03-15 09:00

def is evaluated by name and val by value. This means more or less that val must always return an actual value, while def is more like a promess that you can get a value when evaluating it. For example, if you have a function

def trace(s: => String ) { if (level == "trace") println s } // note the => in parameter definition

that logs an event only if the log level is set to trace and you want to log an objects toString. If you have overriden toString with a value, then you need to pass that value to the trace function. If toString however is a def, it will only be evaluated once it's sure that the log level is trace, which could save you some overhead. def gives you more flexibility, while val is potentially faster

Compilerwise, traits are compiled to java interfaces so when defining a member on a trait, it makes no difference if its a var or def. The difference in performance would depend on how you choose to implement it.

查看更多
登录 后发表回答