Kotlin: изменения, сделанные в супер-конструкторе, перезаписываются

Мне трудно понять, что делает Котлин:

Мой модульный тест выглядит следующим образом:

@Test fun testReadCursorRequest() { val xml = fromFile() val parser: ReadCursorRequestParser = ReadCursorRequestParser(xml) assertEquals(0, parser.status) assertEquals(134, parser.contacts!!.size) } 

Мой парсер выглядит так

 abstract class EnvelopeParser(val xml: String) { abstract fun parseResponse(response: Element) init { parseResponse(xmlFromString(xml)) } // non-related stuff } 

 class ReadCursorRequestParser(xml: String) : EnvelopeParser(xml) { var contacts: List<AddressBookElementParser> = mutableListOf() override fun parseResponse(response: Element) { // here some parsing stuff, fills the contacts-list println("size is: ${contacts.size}") } } 

println говорит, что size is: 134 , модульный тест говорит: java.lang.AssertionError: Expected <134>, actual <0> .

Зачем?

Как вы сказали в комментариях, parseResponse(...) вызывается из конструктора EnvelopeParser .

Тогда что происходит, когда вы создаете экземпляр ReadCursorRequestParser :

  1. Выделяется объект.

  2. ReadCursorRequestParser конструктор ReadCursorRequestParser , который сразу вызывает конструктор суперкласса.

  3. parseResponse(...) EnvelopeParser ) вызывает parseResponse(...) и, таким образом, назначает contacts (и в этот момент это фактически непустой список).

  4. ReadCursorRequestParser затем возвращается, и конструктор ReadCursorRequestParser продолжается.

  5. Конструктор ReadCursorRequestParser снова назначает contacts , теперь это пустой список .

Причиной этого является то, что каждый конструктор сначала вызывает свой супер-конструктор (если он есть) и только затем инициализирует свойства и выполняет блоки init , и все изменения, которые суперконструктор, сделанный для состояния, объявленного в классе (а не базовых классах), будут быть перезаписан собственным конструктором класса.

Этот упрощенный пример показывает это поведение: (link) .


Самое легкое обходное решение – изменить объявление contacts на что-то вроде

 lateinit var contacts: List<AddressBookElementParser> 

С помощью этого объявления конструктор не переназначает contacts .

Но я настоятельно рекомендую вам избегать вызова open функций в конструкторе, потому что, если они переопределены, они могут (и обычно) зависеть от состояния производного класса, которое еще не было инициализировано , а также изменения, которые они делают, будут перезаписывается конструктором производного класса. Вы даже можете закончить с некоторой частью изменений, которые сохраняются, потому что они сделаны для состояния суперкласса, а другая часть стерта – определенно не то, что вы хотите видеть в повседневной жизни.

Intereting Posts
Что отличается между двумя конструкторами? Kotlin добавляет пользовательский прослушиватель для доступа к виджетам на Android В чем разница между Foo :: class.java и Foo :: javaClass? Сложить список для сопряжения с назначением деструктуризации в котлин Kotlin Hibernate OneToOne fetchtype.LAZY немедленно запускает все запросы Шаблон Nullable var внутри строки Получение ошибки «Несовместимые типы» при использовании кода, сгенерированного из класса данных Kotlin Firebase Android – создайте пользователя с помощью электронной почты и пароль в Kotlin Android LiveData Observer не активен после первого обновления В TornadoFX, как я могу изменить одно свойство при изменении других свойств? свойство lateinit не инициализируется при восстановлении активности Почему я не могу поставить {анонимного класса на новую линию в Котлин? Получение информации KProperty изнутри делегированной собственности вне функций getValue () и setValue () Выражение JOOQ Настройка Kotlin в новом проекте Android Studio