Los bloques init y la llamada a super en Kotlin son responsables de la correcta inicialización de objetos teniendo en cuenta la jerarquía de clases. Estos son mecanismos fundamentales que afectan el funcionamiento de las construcciones y la herencia.
En Java, los constructores se pueden llamar explícitamente mediante super(...) o implícitamente por defecto. En Kotlin todo es diferente: existe una clara separación entre constructores primarios y secundarios, y el bloque init se utiliza para la inicialización.
La principal dificultad es entender correctamente cuándo se inicializan las propiedades, cuándo se llaman los bloques init y cómo se construye el orden de llamada a los constructores, especialmente al heredar clases y combinar constructores primarios/secundarios.
Ejemplo de código:
open class Base(val name: String) { init { println("Base init: $name") } } class Derived(name: String, val age: Int): Base(name) { init { println("Derived init: $name, $age") } } fun main() { Derived("Ivan", 25) } // Salida: // Base init: Ivan // Derived init: Ivan, 25
Características clave:
¿En qué orden se ejecutan los bloques init en la cadena de herencia?
Primero se ejecutan los bloques init de la superclase después de la inicialización de sus propiedades, luego los bloques init de la subclase después de sus propias propiedades.
Ejemplo de código:
open class A { init { println("A") } } class B: A() { init { println("B") } } fun main() { B() } // A -> B
¿Se puede llamar a super() fuera de un constructor en Kotlin?
No, la llamada a la superclase se realiza solo como parte de la declaración del constructor proporcionando parámetros al superconstructor.
¿La inicialización del constructor secundario puede no llamar al primario en Kotlin?
No, todos los constructores secundarios deben delegar la llamada a otro constructor secundario o al constructor primario. El primario, a su vez, llama al superconstructor.
Un desarrollador intenta acceder a una propiedad, que se inicializa en la subclase, en el bloque init de la superclase:
open class A { init { println(f()) } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s }``` **Ventajas:** - Uso de polimorfismo. **Desventajas:** - La propiedad s aún no está inicializada, durante la llamada init de la superclase — resultado null o error. ## Caso positivo Separación de la inicialización para no llamar a métodos o propiedades sobreescritos desde el constructor: ```kotlin open class A { init { println("init A") } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s init { println(f()) } }``` **Ventajas:** - No hay acceso a datos no inicializados; - Garantía del orden de inicialización. **Desventajas:** - Aumenta la cantidad de código para una correcta inicialización.