在Scala中,val
可以覆盖def
,但def
无法覆盖val
。
那么,声明一个特征是否有优势,例如像这样:
trait Resource {
val id: String
}
而不是这个?
trait Resource {
def id: String
}
后续问题是:编译器如何在实践中对调用 val
和def
进行不同的处理,以及它实际上对{实际执行了哪种优化{1}} S'编译器坚持认为val
是稳定的 - 在编译器的实践中意味着什么?假设子类实际上使用val
实现id
。将它指定为特征中的val
是否会受到惩罚?
如果我的代码本身不需要def
成员的稳定性,那么在这些情况下始终使用id
并且仅在切换到def
时才算是一种好习惯。这里已经确定了一个性能瓶颈 - 但这可能不太可能吗?
答案 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
函数。如果toString
是def
,则只有在确定日志级别为跟踪后才会对其进行评估,这可以节省一些开销。
def
为您提供了更大的灵活性,而val
可能更快
Compilerwise,traits
被编译为java接口,因此在trait
上定义成员时,如果它是var
或def
则没有区别。性能的差异取决于您选择如何实现它。