在特征中定义def值是否有任何优势?

时间:2012-10-29 16:50:41

标签: scala compiler-optimization traits

在Scala中,val可以覆盖def,但def无法覆盖val

那么,声明一个特征是否有优势,例如像这样:

trait Resource {
  val id: String
}

而不是这个?

trait Resource {
  def id: String
}

后续问题是:编译器如何在实践中对调用 valdef进行不同的处理,以及它实际上对{实际执行了哪种优化{1}} S'编译器坚持认为val是稳定的 - 在编译器的实践中意味着什么?假设子类实际上使用val实现id。将它指定为特征中的val是否会受到惩罚?

如果我的代码本身不需要def成员的稳定性,那么在这些情况下始终使用id并且仅在切换到def时才算是一种好习惯。这里已经确定了一个性能瓶颈 - 但这可能不太可能吗?

4 个答案:

答案 0 :(得分:16)

简答:

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

[*] Java private

答案很长:

我们来看下面的例子:

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

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

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();
}

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

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

    public static void $init$(ResourceDef resourcedef) {}
}

对于val,我们只需在包含类

中调用初始化程序
public abstract class ResourceVal$class {
    public static void $init$(ResourceVal $this) {
        $this.foo$ResourceVal$_setter_$id_$eq("5");
    }
}

当我们开始扩展时:

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

在我们覆盖的地方,我们在类中获得了一个方法,它可以满足你的期望。 def是一种简单的方法:

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

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

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

    private final String id = "6";

    public String foobar() {
        return id();
    }
}

请注意,即使foobar()不使用字段id,也会使用访问者方法。

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

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

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

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

更长的回答:

Josh Suereth在Binary Resilience at Scala Days 2012上做了一个非常有趣的演讲,其中涵盖了这个问题的背景。摘要是:

  

本演讲的重点是JVM上的二进制兼容性及其含义   是二进制兼容的。二元的阴谋的概述   深入介绍了Scala中的不兼容性,然后是一套规则和指南,可帮助开发人员确保自己的   库版本是二进制兼容的和二进制弹性

     

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

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

答案 1 :(得分:2)

不同之处主要在于您可以使用val实现/覆盖def,而不是相反。此外,val仅被评估一次并且每次使用时都会对def进行评估,使用抽象定义中的def将给予混合特征的代码更多关于如何处理和/或优化实现的自由。所以我的观点是,只要没有明确的理由强迫val,就使用defs。

答案 2 :(得分:0)

val表达式在变量声明上被计算一次,它是严格且不可变的。

每次调用时都会重新评估def

答案 3 :(得分:-2)

def按名称评估,val按值评估。这意味着val或多或少必须始终返回实际值,而def更像是一个promess,您可以在评估它时获得一个值。例如,如果您有一个功能

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

仅在日志级别设置为trace并且您要记录对象toString时才记录事件。如果您使用值覆盖toString,则需要将该值传递给trace函数。如果toStringdef,则只有在确定日志级别为跟踪后才会对其进行评估,这可以节省一些开销。  def为您提供了更大的灵活性,而val可能更快

Compilerwise,traits被编译为java接口,因此在trait上定义成员时,如果它是vardef则没有区别。性能的差异取决于您选择如何实现它。