为什么在混合特征时创建匿名类?

Joh*_*ood 6 scala anonymous class mixins

scala> class A
defined class A

scala> trait B
defined trait B
Run Code Online (Sandbox Code Playgroud)

创建类的对象A给我们:

scala> new A
res4: A = A@11ea3fc
Run Code Online (Sandbox Code Playgroud)

但是创建一个混合A了trait 的类对象B会给我们:

scala> new A with B
res3: A with B = $anon$1@172aa3f
Run Code Online (Sandbox Code Playgroud)

这里我们有一个匿名类(暗示anon).为什么?

这是因为该类型A with B被视为一种新类型(并且之前未使用标识符定义)?

axe*_*l22 13

这不仅是因为A with B必须被视为一种新型.对于Scala类型系统,如果存在对应的类则没有直接关系A with B.生成匿名类是因为它必须包含已混合的特征中所有方法的桥接方法.

创建匿名类的原因是该对象必须具有来自的所有方法A和所有方法的实现B.在JVM字节码级别上,这将保证继承多个类,并且JVM上不支持多继承模型.

要模拟多重继承(或mixin组合,但是你想调用它),Scala在创建特征时会做以下事情:

  1. 如果特征T没有方法实现,它会创建一个定义特征中所有方法的接口.
  2. 如果特征T具有方法实现,它还会创建一个类T$class,该类具有每个具体方法的静态方法T.此静态方法与其对应的方法具有相同的主体T,但其签名已更改为包含this参数.如果T有:

    def foo(x: Int) = x
    
    Run Code Online (Sandbox Code Playgroud)

然后T$class会有:

<static> def foo($this: T, x: Int) = x
Run Code Online (Sandbox Code Playgroud)

通过mixin组合获得的类A和某些特征T将生成一个特殊的桥接方法,该方法将调用转发给包含正文的静态方法.这样,方法的主体在每个混入的类中都不会重复T.这就是必须创建匿名类的原因 - 它必须为每个方法定义桥接方法T.

这是一个例子.通过mixin合成创建新类时,例如调用new A with T:

class A {
  def bar = println("!")
}

trait T {
  def foo(x: Int) = x
}

new A with T
Run Code Online (Sandbox Code Playgroud)

编译器将大致重写为以下内容:

class A {
  def bar = println("!")
}

<interface> T {
  def foo(x: Int): Int
}

class T$class {
  <static> def foo($this: T, x: Int) = x
}

class $anon extends A <implements> T {
  // notice that `bar` is inherited, but `foo` is not
  <bridge> def foo(x: Int) = T$class.foo(this, x)
}
new $anon
Run Code Online (Sandbox Code Playgroud)

请注意,编译器实际上可以重写foo调用以直接从调用点调用静态方法,而不是通过桥接方法.它没有这样做的原因是因为它不再支持子类型多态.


0__*_*0__ 6

是.虽然您的类型仍然存在A with B,但需要有一个底层Java类来实现这两个接口.这没有什么不对,除非你以这种方式创建对象数百次,你可能会有数百个类文件.在这种情况下,您可能想要创建一个专用class AB extends A with B然后实例化new AB.

作为旁注,您会发现您也无法直接实例化特征,例如new B不起作用.你也需要在这里创建一个显式类,例如new B {},再次产生一个合成('anonymous')类.

  • 它只会在代码中为每次出现时创建一个新类,而不是为此类的每个实例创建一个新类.因此,除非你在数百个代码行中有"新的A与B",这是不太可能的,所以应该没有问题. (3认同)