2017-01-10 3 views
-1

У меня возникли проблемы с пониманием того, что Котлин на самом деле делает:Котлин: изменения, внесенные в супер конструктор перезаписываются

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

@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>.

Почему?

+0

Внутри парсинга есть ли локальная переменная с именем 'contacts'? Если есть, то его размер печатается, а не размер 'контактов', хранящихся в свойстве. – hotkey

+2

Кроме того, ваш тест, похоже, не вызывает 'parseResponse()'. Где он (или должен быть) вызван? – hotkey

+0

Прошу прощения, если это было непонятно из-за того, что я просто пропустил из этой записи здесь, я хотел сохранить как можно более короткие и контекстно-зависимые. EnvelopeParser фактически вызывает parseResponse от своего конструктора. Фактически, я запускаю это в режиме отладки с точками останова, поэтому на 100% уверены, что локальных переменных контактов нет, IntelliJ отображает значение члена контакта при разбиении на println. –

ответ

5

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

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

  1. Объект выделяется.

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

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

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

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

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

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


Самый простой обходной путь, чтобы изменить объявление о contacts к чему-то вроде

lateinit var contacts: List<AddressBookElementParser> 

С этой декларации, конструктор не будет переназначить contacts.

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

Смежные вопросы