Перейти к содержимому
Теория · Классы · урок 7 из 10

Инициализатор подкласса и final

Представьте карточку животного в ветеринарной клинике. Кличку в неё вписывает регистратура, и такая графа есть у любого животного. У попугая есть ещё своя графа: сколько слов он знает. Её заполняете вы, причём до того, как отдать карточку в регистратуру. Оттуда карточку сразу несут врачу, а он может заглянуть в любую графу.

Dog из урока о наследовании обходился без своего init: init(name:) он получил от Animal. С попугаем так не получится. У него есть своё свойство words без значения по умолчанию, а init(name:) родителя о нём ничего не знает.

Свой init и super.init

Если у подкласса есть своё свойство без значения по умолчанию, ему нужен свой init. В нём сначала задают свои свойства, а потом вызывают super.init, чтобы родитель задал свои:

SWIFT
class Animal {
    var name: String
    init(name: String) {
        self.name = name
    }
}
class Parrot: Animal {
    var words: Int
    init(name: String, words: Int) {
        self.words = words
        super.init(name: name)
    }
}
let kesha = Parrot(name: "Kesha", words: 20)
print(kesha.name, kesha.words) // Kesha 20

Давайте разберём программу:

  1. var words: Int — своё свойство Parrot, и значения по умолчанию у него нет.
  2. init(name: String, words: Int) получает два значения: кличку для родителя и число слов для себя.
  3. self.words = words задаёт свойство Parrot — графу попугая в карточке.
  4. super.init(name: name) вызывает init родителя, и тот задаёт name — кличку, которую вписывает регистратура.
  5. print печатает Kesha 20: у попугая есть и унаследованное свойство name, и своё words.

Получив свой init, Parrot больше не наследует init(name:). Вызов Parrot(name: "Kesha") не скомпилируется, потому что в нём не хватает words:

TEXT
error: missing argument for parameter 'words' in call

Почему сначала свои свойства

Swift требует, чтобы к вызову super.init у всех свойств подкласса уже были значения. Причина в том, что может сделать родитель.

Задав свои свойства, init родителя может вызвать метод экземпляра, например speak(). Если Parrot переопределил этот метод, запустится версия Parrot: версию выбирает класс самого экземпляра. А если она читает words, значение там уже должно быть. Это и есть врач, который заглядывает в любую графу карточки. Порядок обязателен всегда, даже если родитель никаких методов не вызывает.

Здесь картинка с клиникой неточна. Регистратура сама спрашивает кличку, а в Swift её передаёт подкласс: super.init(name: name) отдаёт родителю параметр name, который получил init попугая.

Запрет наследования: final

Слово final запрещает наследовать и переопределять:

  • от класса final class нельзя унаследовать;
  • метод final func нельзя переопределить в подклассе.

Так автор класса защищает его от изменений, которых он не задумывал. Например, если объявить final class Parrot: Animal, то строка class Macaw: Parrot {} не скомпилируется:

TEXT
error: inheritance from a final class 'Parrot'

На собеседовании про final спрашивают и о скорости. Без него версию метода выбирают во время работы программы, по классу экземпляра. Подкласс не напишет свою версию final-метода, поэтому компилятор может вызвать его напрямую, без выбора.

Частая ошибка

В init подкласса сначала вызвали super.init, а потом задали своё свойство:

SWIFT
class Cat: Animal {
    var lives: Int
    init(name: String, lives: Int) {
        super.init(name: name) // ✗
        self.lives = lives
    }
}
TEXT
error: property 'self.lives' not initialized at super.init call

Программа не компилируется: к вызову super.init у свойства lives ещё нет значения. Поменяйте строки местами: сначала self.lives = lives, потом super.init(name: name).

Главное

  • init подкласса сначала задаёт свои свойства, затем вызывает super.init; в обратном порядке программа не скомпилируется.
  • Порядок нужен, потому что init родителя может вызвать переопределённый метод, а тот — прочитать свойство подкласса.
  • Подкласс со своим init не наследует init родителя: Parrot(name: "Kesha") не скомпилируется.
  • От final class нельзя унаследовать, а final func нельзя переопределить.