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

时间:2012-06-28 22:29:04

标签: class scala anonymous mixins

scala> class A
defined class A

scala> trait B
defined trait B

创建类A的对象给我们:

scala> new A
res4: A = A@11ea3fc

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

scala> new A with B
res3: A with B = $anon$1@172aa3f

这里有一个匿名类(由anon暗示)。为什么?

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

2 个答案:

答案 0 :(得分:13)

这不仅是因为A with B必须被视为新类型。对于Scala类型系统,是否存在与A with B对应的类并不重要。生成一个匿名类,因为它必须包含已混合的特征中所有方法的 bridge 方法。

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

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

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

    def foo(x: Int) = x
    
  3. 然后T$class将:

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

    通过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
    

    编译器会将其粗略地重写为:

    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
    

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

答案 1 :(得分:6)

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

作为旁注,您会发现您也无法直接实例化特征,例如: new B无效。你也需要在这里创建一个显式类,例如new B {},再次导致合成('匿名')类。