Мне трудно понять, что делает Котлин:
Мой модульный тест выглядит следующим образом:
@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
:
Выделяется объект.
ReadCursorRequestParser
конструктор ReadCursorRequestParser
, который сразу вызывает конструктор суперкласса.
parseResponse(...)
EnvelopeParser
) вызывает parseResponse(...)
и, таким образом, назначает contacts
(и в этот момент это фактически непустой список).
ReadCursorRequestParser
затем возвращается, и конструктор ReadCursorRequestParser
продолжается.
Конструктор ReadCursorRequestParser
снова назначает contacts
, теперь это пустой список .
Причиной этого является то, что каждый конструктор сначала вызывает свой супер-конструктор (если он есть) и только затем инициализирует свойства и выполняет блоки init
, и все изменения, которые суперконструктор, сделанный для состояния, объявленного в классе (а не базовых классах), будут быть перезаписан собственным конструктором класса.
Этот упрощенный пример показывает это поведение: (link) .
Самое легкое обходное решение – изменить объявление contacts
на что-то вроде
lateinit var contacts: List<AddressBookElementParser>
С помощью этого объявления конструктор не переназначает contacts
.
Но я настоятельно рекомендую вам избегать вызова open
функций в конструкторе, потому что, если они переопределены, они могут (и обычно) зависеть от состояния производного класса, которое еще не было инициализировано , а также изменения, которые они делают, будут перезаписывается конструктором производного класса. Вы даже можете закончить с некоторой частью изменений, которые сохраняются, потому что они сделаны для состояния суперкласса, а другая часть стерта – определенно не то, что вы хотите видеть в повседневной жизни.