模板初始化期间会发生什么?
What happens during template initialization?
If this is the template of a trait then its mixin-evaluation consists
of an evaluation of the statement sequence statsstats.
If this is not a template of a trait, then its evaluation consists of
the following steps.
First, the superclass constructor sc is evaluated.
Then, all base classes in the template's linearization up to the template's
superclass denoted by sc are mixin-evaluated. Mixin-evaluation
happens in reverse order of occurrence in the linearization.
Finally, the statement sequence statsstats is evaluated.
我想知道这里的 "mixin-evaluation" 和 "superclass constructor evaluation" 是什么意思?为什么超类构造函数 sc
与特征 mt1
、mt2
、mt3
等的处理方式不同?
好吧,这是复杂的事情之一,我认为除非您已经知道答案是什么,否则没有一个好的简短答案。我认为简短的回答是,这是因为 Scala 被编译为 JVM 字节码,因此必须匹配目标平台的限制。不幸的是,我认为这个答案并不明确,所以我真正的答案会很长。
免责声明:(对未来的读者来说是可耻的self-promotion):如果你觉得这个很长的答案有用,你也可以看看 到 Lifu Huang 关于类似主题的另一个问题。
免责声明:Java 翻译示例中的代码仅供说明之用。它们的灵感来自 Scala 编译器的实际功能,但在许多细节上与 "real thing" 不匹配。此外,这些示例不能保证工作或编译。除非另有明确说明,否则我将使用(更简单的)代码示例,它们是 Scala 2.12/Java 8 翻译的简化版本,而不是旧的(和更复杂的)Scala/Java 翻译。
关于mix-ins
的一些理论
Mixin is an idea in an object-oriented design to have some more or less encapsulated piece of logic that however doesn't make sense on its own and so it is added to some other classes. This is in a sense similar to multiple inheritance 而多重继承实际上是这个特性在 Scala 中的设计方式。
如果您想在 Scala 中使用 mix-ins 的一些真实示例,这里有一些:
- Scala 集合库实现基于 mix-ins。如果您查看
scala.collection.immutable.List
之类的定义,您会看到很多 mix-ins
sealed abstract class List[+A] extends AbstractSeq[A]
with LinearSeq[A]
with Product // this one is not a mix-in!
with GenericTraversableTemplate[A, List]
with LinearSeqOptimized[A, List[A]] {
在此示例中,mix-ins 用于通过核心方法沿着深入和广泛的 Scala 集合层次结构共享高级方法的实现。
- Cake pattern 用于依赖注入是基于 mix-ins 但这次 mixed-in 逻辑本身根本没有意义。
这里重要的是,在 Scala 中,您可以 mix-in 逻辑(方法)和数据(字段)。
关于Java/JVM和多重继承的一些理论
"Naive" 在 C++ 等语言中完成的多重继承添加了臭名昭著的 Diamond problem. To fix it original design of Java didn't support multiple-inheritance of any logic or fields. You may "extend" exactly one base class (fully inheriting its behavior) and additionally you can "implement" many interfaces which means that the class claims to have all methods from the interface(s) but you can't have any real logic inherited from your base interface. The same restrictions existed in the JVM. 20 years later in Java 8 Default Methods。所以现在你可以继承一些方法但仍然不能继承任何字段。这种在 Scala 2.12 中 mix-ins 的简化实现以要求 Java 8 作为其目标平台为代价。接口仍然不能有 (non-static) 字段,因此不能有构造函数。这是 superclass 构造函数 sc
与特征 mt1
、mt2
、mt3
等 [=98] 被区别对待的主要原因之一=]
同样重要的是要注意 Java 被设计为一种非常安全的语言。特别是它与 "undefined behaviors" 作斗争,如果您 re-use 某些值只是留在内存中(垃圾),则可能会发生这种情况。因此 Java 确保在调用其构造函数之前无法访问基 class 的任何字段。这使得 super
在任何 child 构造函数中几乎强制调用第一行。
Scala 和 mix-ins(简单示例)
现在假设您是 Scala 语言的设计者,您希望它具有 mix-ins 但您的目标平台 (JVM) 不支持它们。你该怎么办?显然你的编译器应该能够将 mix-ins 转换成 JVM 支持的东西。这是一个简单(和废话)示例的粗略近似:
class Base(val baseValue: Int) {
}
trait TASimple {
val aValueNI: AtomicInteger
val aValueI: AtomicInteger = new AtomicInteger(0)
def aIncrementAndGetNI(): Int = aValueNI.incrementAndGet()
def aIncrementAndGetI(): Int = aValueI.incrementAndGet()
}
class SimpleChild(val childValue: Int, baseValue: Int) extends Base(baseValue) with TASimple {
override val aValueNI = new AtomicInteger(5)
}
所以换句话说你有:
- 一个基地 class
Base
有一些领域
- a mix-in trait
TASimple
其中包含 2 个字段(一个初始化,一个未初始化)和两个方法
- 一个childclass
SimpleChild
由于 TASimple
不仅仅是一个方法声明,它不能被编译成一个简单的 Java 接口。它实际上被编译成这样的东西(在 Java 代码中):
public abstract interface TASimple
{
abstract void TASimple_setter_aValueI(AtomicInteger param);
abstract AtomicInteger aValueNI();
abstract AtomicInteger aValueI();
default int aIncrementAndGetNI() { return aValueNI().incrementAndGet(); }
default int aIncrementAndGetI() { return aValueI().incrementAndGet(); }
public static void init(TASimple $this)
{
$this.TASimple_setter_aValueI(new AtomicInteger(0));
}
}
public class SimpleChild extends Base implements TASimple
{
private final int childValue;
private final AtomicInteger aValueNI;
private final AtomicInteger aValueI;
public AtomicInteger aValueI() { return this.aValueI; }
public void TASimple_setter_aValueI(AtomicInteger param) { this.aValueI = param; }
public int childValue() { return this.childValue; }
public AtomicInteger aValueNI() { return this.aValueNI; }
public SimpleChild(int childValue, int baseValue)
{
super(baseValue);
TASimple.init(this);
this.aValueNI = new AtomicInteger(5);
}
}
那么 TASimple
包含什么以及它是如何翻译的(到 Java 8):
aValueNI
和 aValueI
作为 val
声明的一部分。这些必须通过 SimpleChild
用一些字段支持它们来实现(没有任何技巧)。
aIncrementAndGetNI
和 aIncrementAndGetI
方法有一些逻辑。这些方法可以由 SimpleChild
继承,并将基于 aValueNI
和 aValueI
方法工作。
初始化aValueI
的一段逻辑。如果 TASimple
是一个 class,它将有一个构造函数,并且这个逻辑可能已经存在。但是 TASimple
被翻译成一个接口。因此,"constructor" 逻辑片段被移动到 static void init(TASimple $this)
方法,并且 init
从 SimpleChild
构造函数中调用。请注意,Java 规范强制要求 super
调用(即基础 class 的构造函数)必须在它之前调用。
第 3 项中的逻辑是
背后的逻辑
First, the superclass constructor sc is evaluated.
Then, all base classes in the template's linearization up to the template's superclass denoted by sc are mixin-evaluated
同样,这是 JVM 本身强制执行的逻辑:您首先必须调用基本构造函数,然后您才能(并且应该)调用所有其他模拟"constructors" 共 mix-ins。
旁注(Scala pre-2.12/Java pre-8)
在Java8和默认方法之前翻译会更复杂。 TASimple
会被翻译成一个接口,class比如
public abstract interface TASimple
{
public abstract void TASimple_setter_aValueI(AtomicInteger param);
public abstract AtomicInteger aValueNI();
public abstract AtomicInteger aValueI();
public abstract int aIncrementAndGetNI();
public abstract int aIncrementAndGetI();
}
public abstract class TASimpleImpl
{
public static int aIncrementAndGetNI(TASimple $this) { return $this.aValueNI().incrementAndGet(); }
public static int aIncrementAndGetI(TASimple $this) { return $this.aValueI().incrementAndGet(); }
public static void init(TASimple $this)
{
$this.TASimple_setter_aValueI(new AtomicInteger(0));
}
}
public class SimpleChild extends Base implements TASimple
{
private final int childValue;
private final AtomicInteger aValueNI;
private final AtomicInteger aValueI;
public AtomicInteger aValueI() { return this.aValueI; }
public void TASimple_setter_aValueI(AtomicInteger param) { this.aValueI = param; }
public int aIncrementAndGetNI() { return TASimpleImpl.aIncrementAndGetNI(this); }
public int aIncrementAndGetI() { return TASimpleImpl.aIncrementAndGetI(this); }
public int childValue() { return this.childValue; }
public AtomicInteger aValueNI() { return this.aValueNI; }
public SimpleChild(int childValue, int baseValue)
{
super(baseValue);
TASimpleImpl.init(this);
this.aValueNI = new AtomicInteger(5);
}
}
请注意现在 aIncrementAndGetNI
和 aIncrementAndGetI
的实现是如何转移到一些静态方法中的,这些方法采用显式 $this
作为参数。
Scala 和 mix-ins#2(复杂示例)
上一节中的例子说明了一些想法,但不是全部。要获得更详细的说明,需要更复杂的示例。
Mixin-evaluation happens in reverse order of occurrence in the linearization.
当您有多个 mix-ins 时,这部分是相关的,尤其是 diamond problem。考虑以下示例:
trait TA {
val aValueNI0: AtomicInteger
val aValueNI1: AtomicInteger
val aValueNI2: AtomicInteger
val aValueNI12: AtomicInteger
val aValueI: AtomicInteger = new AtomicInteger(0)
def aIncrementAndGetNI0(): Int = aValueNI0.incrementAndGet()
def aIncrementAndGetNI1(): Int = aValueNI1.incrementAndGet()
def aIncrementAndGetNI2(): Int = aValueNI2.incrementAndGet()
def aIncrementAndGetNI12(): Int = aValueNI12.incrementAndGet()
def aIncrementAndGetI(): Int = aValueI.incrementAndGet()
}
trait TB1 extends TA {
val b1ValueNI: AtomicInteger
val b1ValueI: AtomicInteger = new AtomicInteger(1)
override val aValueNI1: AtomicInteger = new AtomicInteger(11)
override val aValueNI12: AtomicInteger = new AtomicInteger(111)
def b1IncrementAndGetNI(): Int = b1ValueNI.incrementAndGet()
def b1IncrementAndGetI(): Int = b1ValueI.incrementAndGet()
}
trait TB2 extends TA {
val b2ValueNI: AtomicInteger
val b2ValueI: AtomicInteger = new AtomicInteger(2)
override val aValueNI2: AtomicInteger = new AtomicInteger(22)
override val aValueNI12: AtomicInteger = new AtomicInteger(222)
def b2IncrementAndGetNI(): Int = b2ValueNI.incrementAndGet()
def b2IncrementAndGetI(): Int = b2ValueI.incrementAndGet()
}
class Base(val baseValue: Int) {
}
class ComplicatedChild(val childValue: Int, baseValue: Int) extends Base(baseValue) with TB1 with TB2 {
override val aValueNI0 = new AtomicInteger(5)
override val b1ValueNI = new AtomicInteger(6)
override val b2ValueNI = new AtomicInteger(7)
}
这里有趣的是 ComplicatedChild
以两种方式继承自 TA
:通过 TB1
和 TB2
。此外,TB1
和 TB2
都定义了 aValueNI12
的一些初始化,但具有不同的值。首先应该提到的是,对于 TA
中定义的每个 val
,ComplicatedChild
将只有一个字段副本。但是如果你尝试这样做会发生什么:
val cc = new inheritance.ComplicatedChild(42, 12345)
println(cc.aIncrementAndGetNI12())
哪个值(TB1
或 TB2
)会获胜?这种行为会是确定性的吗?最后一个问题的答案是——是的,无论是在运行之间还是在编译之间,行为都是确定性的。这是通过所谓的 "traits linearization" 实现的,这是一个完全不同的主题。简而言之,Scala 编译器以某种固定的定义顺序对所有继承的(直接和间接的)特征进行排序,以便它表现出一些良好的行为(例如 parent 特征总是在列表中的 child 特征之后).所以回到报价:
Mixin-evaluation happens in reverse order of occurrence in the linearization.
这个特征线性化顺序确保
在调用某些特征的模拟构造函数时,所有 "base" 字段都已由相应的 parent(模拟)构造函数初始化。
模拟构造函数调用的顺序是固定的,因此行为是确定的。
在这种特殊情况下,线性化顺序为 ComplicatedChild
> TB2
> TB1
> TA
> Base
。这意味着 ComplicatedChild
构造函数实际上被翻译成类似的东西:
public ComplicatedChild(int childValue, int baseValue)
{
super(baseValue);
TA.init(this);
TB1.init(this);
TB2.init(this);
this.aValueNI0 = new AtomicInteger(5);
this.b1ValueNI = new AtomicInteger(6);
this.b2ValueNI = new AtomicInteger(7);
}
等 aValueNI12
将由 TB2
初始化(这将覆盖 TB1
"constructor" 设置的值)。
希望这能澄清一些事情的经过和原因。如果有什么不清楚的地方,请告诉我。
更新(回复评论)
The spec says
Then, all base classes in the template's linearization up to the template's superclass denoted by scsc are mixin-evaluated. Mixin-evaluation happens in reverse order of occurrence in the linearization.
这里的“up to”是什么意思?
让我们扩展 "simple" 示例,添加一个碱基 trait
,如下所示:
trait TX0 {
val xValueI: AtomicInteger = new AtomicInteger(-1)
}
class Base(val baseValue: Int) extends TX0 {
}
trait TASimple extends TX0 {
val aValueNI: AtomicInteger
val aValueI: AtomicInteger = new AtomicInteger(0)
def aIncrementAndGetNI(): Int = aValueNI.incrementAndGet()
def aIncrementAndGetI(): Int = aValueI.incrementAndGet()
}
class SimpleChild(val childValue: Int, baseValue: Int) extends Base(baseValue) with TASimple {
override val aValueNI = new AtomicInteger(5)
}
请注意 TX0
是如何被 BaseClass
和 TASimple
继承的。在这种情况下,我希望线性化产生以下顺序 SimpleChild
> TASimple
> Base
> TX0
> Any
。我对这句话的解释如下:在这种情况下,SimpleChild
的构造函数不会调用 TX0
的 "simulated" 构造函数,因为它按顺序出现在 Base
之后( = sc
)。我认为这种行为的逻辑很明显:从 SimpleChild
构造函数的角度来看,TX0
的 "simulated" 构造函数应该已经被 Base
构造函数调用,此外 Base
可能已经更新了该调用的结果,因此第二次调用 TX0
的 "simulated" 构造函数实际上可能会破坏 Base
.
If this is the template of a trait then its mixin-evaluation consists of an evaluation of the statement sequence statsstats.
If this is not a template of a trait, then its evaluation consists of the following steps.
First, the superclass constructor sc is evaluated.
Then, all base classes in the template's linearization up to the template's superclass denoted by sc are mixin-evaluated. Mixin-evaluation happens in reverse order of occurrence in the linearization.
Finally, the statement sequence statsstats is evaluated.
我想知道这里的 "mixin-evaluation" 和 "superclass constructor evaluation" 是什么意思?为什么超类构造函数 sc
与特征 mt1
、mt2
、mt3
等的处理方式不同?
好吧,这是复杂的事情之一,我认为除非您已经知道答案是什么,否则没有一个好的简短答案。我认为简短的回答是,这是因为 Scala 被编译为 JVM 字节码,因此必须匹配目标平台的限制。不幸的是,我认为这个答案并不明确,所以我真正的答案会很长。
免责声明:(对未来的读者来说是可耻的self-promotion):如果你觉得这个很长的答案有用,你也可以看看
免责声明:Java 翻译示例中的代码仅供说明之用。它们的灵感来自 Scala 编译器的实际功能,但在许多细节上与 "real thing" 不匹配。此外,这些示例不能保证工作或编译。除非另有明确说明,否则我将使用(更简单的)代码示例,它们是 Scala 2.12/Java 8 翻译的简化版本,而不是旧的(和更复杂的)Scala/Java 翻译。
关于mix-ins
的一些理论Mixin is an idea in an object-oriented design to have some more or less encapsulated piece of logic that however doesn't make sense on its own and so it is added to some other classes. This is in a sense similar to multiple inheritance 而多重继承实际上是这个特性在 Scala 中的设计方式。
如果您想在 Scala 中使用 mix-ins 的一些真实示例,这里有一些:
- Scala 集合库实现基于 mix-ins。如果您查看
scala.collection.immutable.List
之类的定义,您会看到很多 mix-ins
sealed abstract class List[+A] extends AbstractSeq[A]
with LinearSeq[A]
with Product // this one is not a mix-in!
with GenericTraversableTemplate[A, List]
with LinearSeqOptimized[A, List[A]] {
在此示例中,mix-ins 用于通过核心方法沿着深入和广泛的 Scala 集合层次结构共享高级方法的实现。
- Cake pattern 用于依赖注入是基于 mix-ins 但这次 mixed-in 逻辑本身根本没有意义。
这里重要的是,在 Scala 中,您可以 mix-in 逻辑(方法)和数据(字段)。
关于Java/JVM和多重继承的一些理论
"Naive" 在 C++ 等语言中完成的多重继承添加了臭名昭著的 Diamond problem. To fix it original design of Java didn't support multiple-inheritance of any logic or fields. You may "extend" exactly one base class (fully inheriting its behavior) and additionally you can "implement" many interfaces which means that the class claims to have all methods from the interface(s) but you can't have any real logic inherited from your base interface. The same restrictions existed in the JVM. 20 years later in Java 8 Default Methods。所以现在你可以继承一些方法但仍然不能继承任何字段。这种在 Scala 2.12 中 mix-ins 的简化实现以要求 Java 8 作为其目标平台为代价。接口仍然不能有 (non-static) 字段,因此不能有构造函数。这是 superclass 构造函数 sc
与特征 mt1
、mt2
、mt3
等 [=98] 被区别对待的主要原因之一=]
同样重要的是要注意 Java 被设计为一种非常安全的语言。特别是它与 "undefined behaviors" 作斗争,如果您 re-use 某些值只是留在内存中(垃圾),则可能会发生这种情况。因此 Java 确保在调用其构造函数之前无法访问基 class 的任何字段。这使得 super
在任何 child 构造函数中几乎强制调用第一行。
Scala 和 mix-ins(简单示例)
现在假设您是 Scala 语言的设计者,您希望它具有 mix-ins 但您的目标平台 (JVM) 不支持它们。你该怎么办?显然你的编译器应该能够将 mix-ins 转换成 JVM 支持的东西。这是一个简单(和废话)示例的粗略近似:
class Base(val baseValue: Int) {
}
trait TASimple {
val aValueNI: AtomicInteger
val aValueI: AtomicInteger = new AtomicInteger(0)
def aIncrementAndGetNI(): Int = aValueNI.incrementAndGet()
def aIncrementAndGetI(): Int = aValueI.incrementAndGet()
}
class SimpleChild(val childValue: Int, baseValue: Int) extends Base(baseValue) with TASimple {
override val aValueNI = new AtomicInteger(5)
}
所以换句话说你有:
- 一个基地 class
Base
有一些领域 - a mix-in trait
TASimple
其中包含 2 个字段(一个初始化,一个未初始化)和两个方法 - 一个childclass
SimpleChild
由于 TASimple
不仅仅是一个方法声明,它不能被编译成一个简单的 Java 接口。它实际上被编译成这样的东西(在 Java 代码中):
public abstract interface TASimple
{
abstract void TASimple_setter_aValueI(AtomicInteger param);
abstract AtomicInteger aValueNI();
abstract AtomicInteger aValueI();
default int aIncrementAndGetNI() { return aValueNI().incrementAndGet(); }
default int aIncrementAndGetI() { return aValueI().incrementAndGet(); }
public static void init(TASimple $this)
{
$this.TASimple_setter_aValueI(new AtomicInteger(0));
}
}
public class SimpleChild extends Base implements TASimple
{
private final int childValue;
private final AtomicInteger aValueNI;
private final AtomicInteger aValueI;
public AtomicInteger aValueI() { return this.aValueI; }
public void TASimple_setter_aValueI(AtomicInteger param) { this.aValueI = param; }
public int childValue() { return this.childValue; }
public AtomicInteger aValueNI() { return this.aValueNI; }
public SimpleChild(int childValue, int baseValue)
{
super(baseValue);
TASimple.init(this);
this.aValueNI = new AtomicInteger(5);
}
}
那么 TASimple
包含什么以及它是如何翻译的(到 Java 8):
aValueNI
和aValueI
作为val
声明的一部分。这些必须通过SimpleChild
用一些字段支持它们来实现(没有任何技巧)。aIncrementAndGetNI
和aIncrementAndGetI
方法有一些逻辑。这些方法可以由SimpleChild
继承,并将基于aValueNI
和aValueI
方法工作。初始化
aValueI
的一段逻辑。如果TASimple
是一个 class,它将有一个构造函数,并且这个逻辑可能已经存在。但是TASimple
被翻译成一个接口。因此,"constructor" 逻辑片段被移动到static void init(TASimple $this)
方法,并且init
从SimpleChild
构造函数中调用。请注意,Java 规范强制要求super
调用(即基础 class 的构造函数)必须在它之前调用。
第 3 项中的逻辑是
背后的逻辑First, the superclass constructor sc is evaluated.
Then, all base classes in the template's linearization up to the template's superclass denoted by sc are mixin-evaluated
同样,这是 JVM 本身强制执行的逻辑:您首先必须调用基本构造函数,然后您才能(并且应该)调用所有其他模拟"constructors" 共 mix-ins。
旁注(Scala pre-2.12/Java pre-8)
在Java8和默认方法之前翻译会更复杂。 TASimple
会被翻译成一个接口,class比如
public abstract interface TASimple
{
public abstract void TASimple_setter_aValueI(AtomicInteger param);
public abstract AtomicInteger aValueNI();
public abstract AtomicInteger aValueI();
public abstract int aIncrementAndGetNI();
public abstract int aIncrementAndGetI();
}
public abstract class TASimpleImpl
{
public static int aIncrementAndGetNI(TASimple $this) { return $this.aValueNI().incrementAndGet(); }
public static int aIncrementAndGetI(TASimple $this) { return $this.aValueI().incrementAndGet(); }
public static void init(TASimple $this)
{
$this.TASimple_setter_aValueI(new AtomicInteger(0));
}
}
public class SimpleChild extends Base implements TASimple
{
private final int childValue;
private final AtomicInteger aValueNI;
private final AtomicInteger aValueI;
public AtomicInteger aValueI() { return this.aValueI; }
public void TASimple_setter_aValueI(AtomicInteger param) { this.aValueI = param; }
public int aIncrementAndGetNI() { return TASimpleImpl.aIncrementAndGetNI(this); }
public int aIncrementAndGetI() { return TASimpleImpl.aIncrementAndGetI(this); }
public int childValue() { return this.childValue; }
public AtomicInteger aValueNI() { return this.aValueNI; }
public SimpleChild(int childValue, int baseValue)
{
super(baseValue);
TASimpleImpl.init(this);
this.aValueNI = new AtomicInteger(5);
}
}
请注意现在 aIncrementAndGetNI
和 aIncrementAndGetI
的实现是如何转移到一些静态方法中的,这些方法采用显式 $this
作为参数。
Scala 和 mix-ins#2(复杂示例)
上一节中的例子说明了一些想法,但不是全部。要获得更详细的说明,需要更复杂的示例。
Mixin-evaluation happens in reverse order of occurrence in the linearization.
当您有多个 mix-ins 时,这部分是相关的,尤其是 diamond problem。考虑以下示例:
trait TA {
val aValueNI0: AtomicInteger
val aValueNI1: AtomicInteger
val aValueNI2: AtomicInteger
val aValueNI12: AtomicInteger
val aValueI: AtomicInteger = new AtomicInteger(0)
def aIncrementAndGetNI0(): Int = aValueNI0.incrementAndGet()
def aIncrementAndGetNI1(): Int = aValueNI1.incrementAndGet()
def aIncrementAndGetNI2(): Int = aValueNI2.incrementAndGet()
def aIncrementAndGetNI12(): Int = aValueNI12.incrementAndGet()
def aIncrementAndGetI(): Int = aValueI.incrementAndGet()
}
trait TB1 extends TA {
val b1ValueNI: AtomicInteger
val b1ValueI: AtomicInteger = new AtomicInteger(1)
override val aValueNI1: AtomicInteger = new AtomicInteger(11)
override val aValueNI12: AtomicInteger = new AtomicInteger(111)
def b1IncrementAndGetNI(): Int = b1ValueNI.incrementAndGet()
def b1IncrementAndGetI(): Int = b1ValueI.incrementAndGet()
}
trait TB2 extends TA {
val b2ValueNI: AtomicInteger
val b2ValueI: AtomicInteger = new AtomicInteger(2)
override val aValueNI2: AtomicInteger = new AtomicInteger(22)
override val aValueNI12: AtomicInteger = new AtomicInteger(222)
def b2IncrementAndGetNI(): Int = b2ValueNI.incrementAndGet()
def b2IncrementAndGetI(): Int = b2ValueI.incrementAndGet()
}
class Base(val baseValue: Int) {
}
class ComplicatedChild(val childValue: Int, baseValue: Int) extends Base(baseValue) with TB1 with TB2 {
override val aValueNI0 = new AtomicInteger(5)
override val b1ValueNI = new AtomicInteger(6)
override val b2ValueNI = new AtomicInteger(7)
}
这里有趣的是 ComplicatedChild
以两种方式继承自 TA
:通过 TB1
和 TB2
。此外,TB1
和 TB2
都定义了 aValueNI12
的一些初始化,但具有不同的值。首先应该提到的是,对于 TA
中定义的每个 val
,ComplicatedChild
将只有一个字段副本。但是如果你尝试这样做会发生什么:
val cc = new inheritance.ComplicatedChild(42, 12345)
println(cc.aIncrementAndGetNI12())
哪个值(TB1
或 TB2
)会获胜?这种行为会是确定性的吗?最后一个问题的答案是——是的,无论是在运行之间还是在编译之间,行为都是确定性的。这是通过所谓的 "traits linearization" 实现的,这是一个完全不同的主题。简而言之,Scala 编译器以某种固定的定义顺序对所有继承的(直接和间接的)特征进行排序,以便它表现出一些良好的行为(例如 parent 特征总是在列表中的 child 特征之后).所以回到报价:
Mixin-evaluation happens in reverse order of occurrence in the linearization.
这个特征线性化顺序确保
在调用某些特征的模拟构造函数时,所有 "base" 字段都已由相应的 parent(模拟)构造函数初始化。
模拟构造函数调用的顺序是固定的,因此行为是确定的。
在这种特殊情况下,线性化顺序为 ComplicatedChild
> TB2
> TB1
> TA
> Base
。这意味着 ComplicatedChild
构造函数实际上被翻译成类似的东西:
public ComplicatedChild(int childValue, int baseValue)
{
super(baseValue);
TA.init(this);
TB1.init(this);
TB2.init(this);
this.aValueNI0 = new AtomicInteger(5);
this.b1ValueNI = new AtomicInteger(6);
this.b2ValueNI = new AtomicInteger(7);
}
等 aValueNI12
将由 TB2
初始化(这将覆盖 TB1
"constructor" 设置的值)。
希望这能澄清一些事情的经过和原因。如果有什么不清楚的地方,请告诉我。
更新(回复评论)
The spec says
Then, all base classes in the template's linearization up to the template's superclass denoted by scsc are mixin-evaluated. Mixin-evaluation happens in reverse order of occurrence in the linearization.
这里的“up to”是什么意思?
让我们扩展 "simple" 示例,添加一个碱基 trait
,如下所示:
trait TX0 {
val xValueI: AtomicInteger = new AtomicInteger(-1)
}
class Base(val baseValue: Int) extends TX0 {
}
trait TASimple extends TX0 {
val aValueNI: AtomicInteger
val aValueI: AtomicInteger = new AtomicInteger(0)
def aIncrementAndGetNI(): Int = aValueNI.incrementAndGet()
def aIncrementAndGetI(): Int = aValueI.incrementAndGet()
}
class SimpleChild(val childValue: Int, baseValue: Int) extends Base(baseValue) with TASimple {
override val aValueNI = new AtomicInteger(5)
}
请注意 TX0
是如何被 BaseClass
和 TASimple
继承的。在这种情况下,我希望线性化产生以下顺序 SimpleChild
> TASimple
> Base
> TX0
> Any
。我对这句话的解释如下:在这种情况下,SimpleChild
的构造函数不会调用 TX0
的 "simulated" 构造函数,因为它按顺序出现在 Base
之后( = sc
)。我认为这种行为的逻辑很明显:从 SimpleChild
构造函数的角度来看,TX0
的 "simulated" 构造函数应该已经被 Base
构造函数调用,此外 Base
可能已经更新了该调用的结果,因此第二次调用 TX0
的 "simulated" 构造函数实际上可能会破坏 Base
.