¿Cómo se manejan las properties anuladas en los bloques de inicio?

Estoy tratando de entender por qué el siguiente código arroja:

open class Base(open val input: String) { lateinit var derived: String init { derived = input.toUpperCase() // throws! } } class Sub(override val input: String) : Base(input) 

Al invocar este código de esta manera:

 println(Sub("test").derived) 

arroja una exception, porque en el momento en que se llama toUpperCase , la input resuelve en null . Encuentro este contador intuitivo: paso un valor no nulo al constructor primario, pero en el bloque de inicio de la superclass se resuelve nulo?

Creo que tengo una vaga idea de lo que podría estar pasando: dado que la input sirve tanto como un argumento constructor como una propiedad, la asignación llama internamente a this.input , pero aún no se ha inicializado por completo. Es realmente extraño: en el depurador IntelliJ, la input resuelve normalmente (al valor "testing"), pero tan pronto como invoco la window de evaluación de expresión e inspecciono la input manualmente, de repente es nula.

Suponiendo que este es el comportamiento esperado, ¿qué recomiendas hacer en su lugar, es decir, cuando uno necesita inicializar campos derivados de properties de la misma class?

ACTUALIZACIÓN: He publicado dos fragments de código aún más concisos que ilustran de dónde proviene la confusión:

https://gist.github.com/mttkay/9fbb0ddf72f471465afc https://gist.github.com/mttkay/5dc9bde1006b70e1e8ba

El ejemplo original es equivalente al siguiente progtwig Java:

 class Base { private String input; private String derived; Base(String input) { this.input = input; this.derived = getInput().toUpperCase(); // Initializes derived by calling an overridden method } public String getInput() { return input; } } class Derived extends Base { private String input; public Derived(String input) { super(input); // Calls the superclass constructor, which tries to initialize derived this.input = input; // Initializes the subclass field } @Override public String getInput() { return input; // Returns the value of the subclass field } } 

El método getInput () se reemplaza en la class Sub, por lo que el código llama a Sub.getInput (). En este momento, el constructor de la class Sub no se ha ejecutado, por lo que el campo de respaldo que contiene el valor de Sub.input sigue siendo nulo. Esto no es un error en Kotlin; fácilmente puede encontrarse con el mismo problema en el código Java puro.

La solución es no anular la propiedad. (He visto tu comentario, pero esto realmente no explica por qué crees que debes anularlo).

La confusión proviene del hecho de que creó dos almacenes para el valor de input (campos en JVM). Uno está en la class base, uno en derivado. Cuando está leyendo input valor de input en la class base, llama getInput método getInput virtual bajo el capó. getInput se reemplaza en la class derivada para devolver su propio valor almacenado, que no se inicializa antes de llamar al constructor base. Este es un problema típico de "llamada virtual en el constructor".

Si cambias la class derivada para usar realmente la propiedad de súper tipo, todo vuelve a estar bien.

 class Sub(input: String) : Base(input) { override val input : String get() = super.input } 
  • Repetir una alarma para disparar exacta en el momento específico no funciona correctamente
  • No se puede acceder a la class externa desde una class anónima
  • Enlazar list de objects usando Guice + Kotlin
  • Cómo depurar secuencias / collections de Kotlin
  • ¿Cómo orderar LinkedHashMap por valores en Kotlin?
  • ¿Puedo invocar el complemento noargs de Kotlin desde la command-line o desde Ant?
  • ¿Es posible mezclar Scala y Kotlin en el mismo module maven?
  • ¿Cómo explicar este extraño comportamiento al establecer márgenes programáticamente a una vista dentro de RelativeLayout?
  • RxJava Observable.create envolver suscripciones observables
  • ¿Las coroutines de kotlin tienen llamada asincrónica con timer?
  • Forma idiomática de volver si no nulo en Kotlin