En la definición de AbstractElement
, en la práctica se está definiendo un constructor que inicializa el contenido para anular y computa contents.length. El constructor de UnifiedElement
llama al constructor AbstractElement
y solo luego inicializa el contenido.
EDITAR: en otras palabras, tenemos una nueva instancia de un problema ya existente en Java (y cualquier lenguaje de programación orientada a objetos): el constructor de una superclase llama a un método implementado en una subclase, pero este último no puede ser de forma segura llamado porque la subclase aún no está construida. Los vals abstractos son solo una de las formas de activarlo.
La solución más simple aquí es simplemente hacer height
un def
, que es mejor anwyay, y tenga en cuenta las reglas de inicialización vinculadas en la otra respuesta.
abstract class AbstractElement {
val contents: Array[String]
def height: Int = contents.length //Make this a def
}
La solución ligeramente más complejo, en cambio, es la fuerza contents
que ser inicializado antes altura, lo que se puede hacer con esta sintaxis:
class UnifiedElement(ch: Char, _width: Int, _height: Int) extends {
val contents = Array.fill(_height)(ch.toString() * _width)
} with AbstractElement {
//...
}
Tenga en cuenta que la composición mixin, es decir with
, no es simétrico, funciona de izquierda a derecha. Y tenga en cuenta que al final puede omitirse {}
, si no define otros miembros.
Lazy vals también son una solución, pero incurren en bastante sobrecarga de tiempo de ejecución: cada vez que lee la variable, el código generado leerá un mapa de bits volátil para verificar que el campo ya se haya inicializado.
Fabricación contents
a def
aquí parece una mala idea, porque se recalcula con demasiada frecuencia.
Por último, evitar valores abstractos es en mi humilde opinión una medida extrema. A veces son lo correcto, solo debes tener cuidado con vals concretos que se refieren a valores abstractos.
EDIT: Parece que en lugar de un valor abstracto, uno podría usar una definición abstracta y anularla con un valor concreto. Eso es posible, pero no ayuda si hay vals concretos que se refieren a la definición abstracta. Considere esta variante del código anterior, y prestar atención a cómo se definen los miembros:
abstract class AbstractElement {
def contents: Array[String]
val height: Int = contents.length // line 3
}
class UnifiedElement(ch: Char, _width: Int, _height: Int) extends AbstractElement {
val contents = Array.fill(_height)(ch.toString() * _width)
}
Este código tiene el mismo comportamiento en tiempo de ejecución como el código dado por la OP, incluso si AbstractElement.contents
es ahora def
: el cuerpo de el descriptor de acceso lee un campo que solo se inicializa por el constructor de la subclase. La única diferencia entre un valor abstracto y una definición abstracta parece ser que un valor abstracto solo puede ser anulado por un valor concreto, por lo que puede ser útil restringir el comportamiento de las subclases si eso es lo que desea.
¿Puede aclarar 'Siempre use abstract 'def's y' lazy val's? El compilador no le permite poner 'lazy val' en una clase abstracta. – jbx
@jbx, sí, tienes razón. Ese solo aplica para los rasgos. – missingfaktor
Algunos ejemplos que veo usan un 'val' en la clase abstracta y luego' perezoso val' en la clase concreta que lo extiende. ¿Es esta la manera correcta? (Todavía estoy aprendiendo Scala, así que me confundo un poco) – jbx