在Scala official docs中,它说:
如果这是特征的模板,则其mixin-evaluation包含 对statsstats语句序列的评估。
如果这不是特质的模板,那么它的评估包括 以下步骤。
首先,评估超类构造函数sc。
然后,模板中的所有基类都会线性化直至模板 由sc表示的超类是混合评估的。 mixin的评价 在线性化中以相反的顺序发生。
最后,评估语句序列statsstats。
我想知道" mixin-evaluation"和"超类构造函数评估"这意味着什么为什么超类构造函数sc
与特征mt1
,mt2
,mt3
等区别对待?
答案 0 :(得分:1)
嗯,这是一个复杂的事情,我不认为有一个很好的简短回答,除非你已经知道答案是什么。我认为简短的回答是,这是因为Scala被编译为JVM字节码,因此必须匹配该目标平台的限制。不幸的是,我不认为这个答案很明确,所以我的答案很长。
免责声明 :(对未来的读者来说是一种可耻的自我推销):如果你觉得这个很长的答案很有用,你也可以看一下利福的另一个问题my another long answer黄对类似的话题。
免责声明:提供翻译示例中的Java代码仅用于说明目的。他们的灵感来自于Scala编译器的实际功能,但并不匹配"真实的东西"在许多细节。此外,这些例子不能保证工作或编译。除非明确提到,否则我将使用(更简单的)代码示例,这些示例是Scala 2.12 / Java 8转换的简化版本,而不是旧的(和更复杂的)Scala / Java转换。
关于混合的一些理论
Mixin是面向对象设计中的一个想法,它有一些或多或少的封装逻辑,然而它本身并没有意义,所以它被添加到其他一些类中。这在某种意义上类似于multiple inheritance,多重继承实际上是Scala中设计此功能的方式。
如果您想在Scala中使用一些真实的混合示例,请参阅以下内容:
scala.collection.immutable.List
之类的定义,你会看到很多混合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集合层次结构中的核心方法共享高级方法的实现。
重要的是,在Scala中,您可以混合使用逻辑(方法)和数据(字段)。
关于Java / JVM和多重继承的一些理论
"朴素"像C ++这样的语言所做的多重继承有一个臭名昭着的Diamond problem。为了解决这个问题,Java的原始设计并不支持任何逻辑或字段的多重继承。你可以"延伸"一个基类(完全继承其行为),另外你可以实现"许多接口意味着该类声称拥有来自接口的所有方法,但是您不能拥有从您的基接口继承的任何实际逻辑。 JVM中存在相同的限制。 20年后,在Java 8 Default Methods中添加了diamond problem。所以现在你可以继承一些方法,但仍然不能继承任何字段。这简化了Scala 2.12中混合的实现,其代价是要求Java 8作为其目标平台。静止接口不能具有(非静态)字段,因此不能具有构造函数。这是超类构造函数sc
与特征mt1
,mt2
,mt3
等区别对待的主要原因之一。
另外值得注意的是,Java被设计为一种非常安全的语言。特别是它反对"未定义的行为"如果你重新使用一些只是内存中剩余(垃圾)的值,可能会发生这种情况。因此,Java确保您无法访问基类的任何字段,直到调用其构造函数为止。这使得super
在任何子构造函数中调用了非常强制性的第一行。
Scala和混合(简单示例)
现在假设您是Scala语言的设计者,并且希望它具有混合,但您的目标平台(JVM)并不支持它们。你该怎么办?显然,您的编译器应该能够将混合内容转换为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)
}
所以用言语表示:
Base
TASimple
,其中包含2个字段(一个初始化,一个未初始化)和两个方法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
是一个类,它将有一个构造函数,这个逻辑可能就在那里。但是TASimple
被转换为接口。因此,#34;构造函数"将一段逻辑移到static void init(TASimple $this)
方法,并从init
构造函数调用SimpleChild
。请注意,Java规范强制要求在它之前调用super
调用(即基类的构造函数)。
第3项中的逻辑是背后的原因
首先,评估超类构造函数sc。
然后,模板的线性化中直到由sc表示的模板超类的所有基类都是混合评估的
这也是JVM本身的逻辑强制执行:首先必须调用基础构造函数,然后才能(并且应该)调用所有其他模拟的#34;构造函数"所有混音。
旁注(Scala pre-2.12 / Java pre-8)
在Java 8和默认方法之前,翻译会更加复杂。 TASimple
将被转换为接口和类,例如
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和混合#2 (复杂的例子)
上一节中的示例说明了一些想法,但并非全部。有关更详细的说明,则需要更复杂的示例。
混合评估以线性化中出现的相反顺序发生。
当您有多个混音时,尤其是{{3}}的情况下,此部分是相关的。请考虑以下示例:
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
的一些初始化,但具有不同的值。首先,应该提到ComplicatedChild
只有val
中定义的每个TA
的一个字段副本。但是如果你试试这会发生什么:
val cc = new inheritance.ComplicatedChild(42, 12345)
println(cc.aIncrementAndGetNI12())
哪个值(TB1
或TB2
)会获胜?这种行为是否具有决定性?最后一个问题的答案是 - 是的,行为和编辑之间的行为将是确定性的。这是通过所谓的'特征线性化"来实现的。这是一个完全不同的话题。简而言之,Scala编译器以一些固定的定义顺序对所有继承的(直接和间接)特征进行排序,以便它表现出一些良好的行为(例如父特征总是在其列表中的子特征之后)。所以回到引用:
混合评估以线性化中出现的相反顺序发生。
此特征线性化顺序确保
所有" base"在调用某些特征的模拟构造函数时,相应的父(模拟)构造函数已经初始化了字段。
模拟构造函数调用的顺序是固定的,因此行为是确定性的。
在这种特殊情况下,线性化顺序为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
"构造函数")设置的值。
希望这能澄清一下发生了什么以及为什么。如果有什么不清楚,请告诉我。
更新(回复评论)
规范说
然后,模板的线性化中直到由scsc表示的模板超类的所有基类被混合评估。混合评估以线性化中出现的相反顺序发生。
“ 最多 ”在这里的含义是什么?
让我们扩展"简单"示例如下添加一个基础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
的构造函数,因为它出现在Base
(= sc
)之后的顺序中。我认为这种行为的逻辑是显而易见的:从SimpleChild
构造函数的角度来看,"模拟" TX0
的构造函数应该已经由Base
构造函数调用,而且Base
可能已更新该调用的结果,因此调用"模拟"第二次TX0
的构造函数实际上可能会破坏Base
。