将val定义为特征中的def是否有任何优势?

Jea*_*let 13 scala traits compiler-optimization

在Scala中,a val可以覆盖a def,但是a def不能覆盖a val.

那么,声明一个特征是否有利于这样:

trait Resource {
  val id: String
}
Run Code Online (Sandbox Code Playgroud)

而不是这个?

trait Resource {
  def id: String
}
Run Code Online (Sandbox Code Playgroud)

后续问题是:编译器如何在实践中对调用 val s和defs进行不同的处理,以及它实际上对vals进行了哪种优化?编译器坚持认为vals是稳定的 - 在编译器的实践中意味着什么?假设子类实际上是id用a 实现的val.如果将其指定为def特征,是否会受到惩罚?

如果我的代码本身不需要id成员的稳定性,那么def在这些情况下总是使用s并且val仅在此处识别出性能瓶颈时才切换到s是一种良好的做法- 不管这可能是多么不可能?

Mat*_*ell 16

简短回答:

据我所知,值总是通过访问器方法访问.使用def定义一个返回值的简单方法.使用使用访问器方法val定义私有[*] final字段.因此,在访问方面,两者之间的差别很小.差异是概念性的,def每次都要重新评估,并且val只评估一次.这显然会对性能产生影响.

[*] Java私有

答案很长:

我们来看下面的例子:

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

trait ResourceVal {
  val id: String = "5"
}
Run Code Online (Sandbox Code Playgroud)

所述ResourceDef&ResourceVal产生相同的代码,忽略初始化:

public interface ResourceVal extends ScalaObject {
    volatile void foo$ResourceVal$_setter_$id_$eq(String s);
    String id();
}

public interface ResourceDef extends ScalaObject {
    String id();
}
Run Code Online (Sandbox Code Playgroud)

对于生成的子类(包含方法的实现),ResourceDef产生正如您所期望的那样,注意该方法是静态的:

public abstract class ResourceDef$class {
    public static String id(ResourceDef $this) {
        return "5";
    }

    public static void $init$(ResourceDef resourcedef) {}
}
Run Code Online (Sandbox Code Playgroud)

对于val,我们只需在包含类中调用初始化程序

public abstract class ResourceVal$class {
    public static void $init$(ResourceVal $this) {
        $this.foo$ResourceVal$_setter_$id_$eq("5");
    }
}
Run Code Online (Sandbox Code Playgroud)

当我们开始扩展时:

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
Run Code Online (Sandbox Code Playgroud)

在我们覆盖的地方,我们在类中得到一个方法,它只是做你期望的.def很简单:

public class ResourceDefClass implements ResourceDef, ScalaObject {
    public String id() {
        return "6";
    }
}
Run Code Online (Sandbox Code Playgroud)

val定义了一个私有字段和访问器方法:

public class ResourceValClass implements ResourceVal, ScalaObject {
    public String id() {
        return id;
    }

    private final String id = "6";

    public String foobar() {
        return id();
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,即使foobar()不使用该字段id,也使用访问器方法.

最后,如果我们不覆盖,那么我们得到一个方法,它调用特征辅助类中的静态方法:

public class ResourceNoneClass implements ResourceDef, ScalaObject {
    public volatile String id() {
        return ResourceDef$class.id(this);
    }
}
Run Code Online (Sandbox Code Playgroud)

我在这些例子中删除了构造函数.

因此,始终使用访问器方法.我认为这是为了避免在扩展可以实现相同方法的多个特征时出现复杂情况.它很快就变得复杂了.

甚至更长的答案:

Josh Suereth 在2012年Scala Days上就二元弹性做了一个非常有趣的演讲,其中介绍了这个问题的背景.摘要是:

本演讲重点讨论JVM上的二进制兼容性以及二进制兼容的含义.深入介绍了Scala中二进制不兼容性的概述 ,然后介绍了一组规则和指南,这些规则和指南将帮助开发人员确保他们自己的库版本具有二进制兼容性和二进制弹性.

特别是,这个讲话着眼于:

  • 特征和二进制兼容性
  • Java序列化和匿名类
  • 懒惰的vals隐藏的创作
  • 开发具有二进制弹性的代码