Esto no es solo porque A with B
debe considerarse como un nuevo tipo. Para el sistema de tipo Scala, no importa directamente si existe una clase correspondiente a A with B
. se genera una clase anónima porque tiene que contener puente métodos para todos los métodos en los rasgos que han sido mezclados.
La razón de que una clase anónima se crea es que el objeto debe tener implementaciones de todos los métodos de A
y todos los métodos desde B
. En el nivel de código de byte JVM, esto garantizaría la herencia de múltiples clases, y el modelo de herencia múltiple no es compatible con la JVM.
Para simular la herencia múltiple (o composición mixin, sin embargo desea llamar), Scala hace las siguientes cosas cuando se crea un rasgo:
- Si el rasgo
T
no tiene implementaciones de métodos, se crea una interfaz que define todos los métodos en el rasgo.
Si el rasgo T
tiene implementaciones de métodos, también crea una clase T$class
que tiene un método estático para cada uno de los métodos concretos en T
. Este método estático tiene el mismo cuerpo que su método correspondiente en T
, pero su firma se cambia para incluir el parámetro this
.Si T
tenían:
def foo(x: Int) = x
entonces T$class
tendrá:
<static> def foo($this: T, x: Int) = x
La clase obtenida por composición mixin de alguna clase A
y algún rasgo T
entonces tendrá un método puente especial generado que reenvía la llamada al método estático que contiene el cuerpo. De esta forma, el cuerpo del método no se duplica en todas las clases que se mezclan en T
. Esta es la razón por la que debe crearse la clase anónima; debe tener métodos puente definidos para cada método en T
.
Aquí hay un ejemplo. Cuando creas una nueva clase haciendo mixin composition, p. llaman new A with T
:
class A {
def bar = println("!")
}
trait T {
def foo(x: Int) = x
}
new A with T
el compilador volver a escribir más o menos a algo como esto:
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
en cuenta que el compilador en realidad podría reescribir los callsites a foo
para llamar a los métodos estáticos directamente desde el callsite, en vez que a través de un método de puente. La razón por la que no se hace de esa manera es porque ya no sería compatible con subtipar el polimorfismo.
+1 especialmente para mostrar un ejemplo! –
Excelente respuesta, muchas gracias. –
Tenga en cuenta que al llamar 'scalac' con el argumento' -Xprint: mixin' se mostrará la construcción exacta que crea Scala. '-Xshow-phases' muestra otras fases que se pueden usar con' -Xprint: '. – outis