Обучение на примере

Котлин: Конструкторы

Как и многие другие разработчики, работающие с Android, я начал писать приложения на Java, а затем перешел на Kotlin, поскольку он стал более широко принят Google и сообществом разработчиков Android.

Одно из первых отличий, с которыми я столкнулся при переходе с Java на Kotlin, заключалось в том, как определяются конструкторы.

В Java у вас есть ноль или более явно определенных конструкторов. Если вы не определяете конструктор, то для вас создается конструктор по умолчанию без аргументов. Конструкторы определены в теле класса Java, и каждый определенный конструктор отличается от других перегрузкой своего списка параметров.

И наоборот, в Kotlin у вас есть так называемый первичный конструктор, который необязательно определяется в сигнатуре класса. В дополнение к первичному конструктору вы можете определить ноль или более вторичных конструкторов.

Ниже приведен пример простого первичного конструктора в Котлине:

class Dog constructor(val name: String) {}

Здесь следует отметить несколько моментов:

  • Использование ключевого слова constructor и его отображение в сигнатуре класса до определения тела. Это ключевое слово не является обязательным в этом примере и требуется только при использовании аннотаций (например, @Inject) или модификаторов видимости (например, при создании закрытого конструктора)
  • Основной конструктор не может содержать никакого кода. Код инициализации, который обычно появляется в этом конструкторе, вместо этого должен находиться в одном или нескольких блоках инициализации (init).

При наличии этой информации приведенный выше пример можно переписать, чтобы опустить ключевое слово конструктор, и результат будет таким же:

class Dog (val name: String)

Примером первичного конструктора, в котором действительно потребуется ключевое слово constructor, может быть класс, экземпляр которого вы не хотите создавать извне кода класса. Например, возможно, вы хотите, чтобы все новые экземпляры вашего класса Dog создавались с помощью фабричного метода:

class Dog private constructor(val name: String) {

    companion object {
        fun newDog(name: String) = Dog(name)
    }
}

Еще одно различие между тем, как работают конструкторы в Kotlin по сравнению с Java, заключается в том, как они вызываются, или, другими словами, как создаются новые экземпляры. В Kotlin нет нового ключевого слова. Вместо этого аналогично создаются новые экземпляры класса:

val dog01 = Dog(name = "Sparky")

В Kotlin, если вы явно не определяете первичный конструктор, он будет создан для вас. Например, вполне допустим следующий код:

class EmptyDog

Новые экземпляры EmptyDog можно создать, вызвав сгенерированный первичный конструктор без аргументов следующим образом:

val emptyDog = EmptyDog()

Если вы хотите, чтобы при создании экземпляра вашего класса выполнялся какой-либо тип кода инициализации, вы должны сделать это с одним или несколькими блоками инициализации:

class Dog constructor(val name: String) {

    init {
        println("Registering $name with the AKC")
        registerDogWithAKC()
    }

    private fun registerDogWithAKC() {
        // TODO: perform registration tasks
    }
}

Несколько замечаний по блокам инициализации:

  • Блоки инициализации связаны с основным конструктором
  • Независимо от того, определяете ли вы основной конструктор явно или нет, каждый определенный блок инициализации будет запускаться при создании экземпляра вашего класса.
  • Если определено более одного блока инициализации, они будут выполняться в том порядке, в котором они появляются в теле вашего класса.
  • На поля, которые появляются в основном конструкторе, можно ссылаться из блоков инициализации (в приведенном выше примере вы можете видеть, что на поле name есть ссылка в блоке инициализации)
  • Если вы определили какие-либо вторичные конструкторы, обратите внимание, что определенные блоки инициализации будут выполняться до выполнения тела любого вторичного конструктора.

Если говорить о вторичных конструкторах, то теперь о них и поговорим :)

Из-за отсутствия аргументов по умолчанию в Java вы часто будете видеть анти-шаблон, известный как Telescoping Constructor, где конструкторы перегружаются снова и снова, придавая ему вид телескопирования в редакторе или диаграмме классов. Ниже приведен краткий пример этого антипаттерна:

private String name;
private String breed;
private boolean registered;

public Dog() {
    this("Scruffy");
}
public Dog(String name) {
    this(name, "Terrier");
}
public Dog(String name, String breed) {
    this(name, breed, false);
}
public Dog(String name, String breed, boolean registered) {
    this.name = name;
    this.breed = breed;
    this.registered = registered;
}

Этого анти-шаблона можно избежать, используя шаблон Builder, но в Kotlin в этом по большей части нет необходимости, потому что с Kotlin вы можете объявить значения по умолчанию для параметров конструктора:

class Dog (val name: String, 
           val breed: String = "Terrier", 
           val registered: Boolean = false)

С учетом сказанного есть еще много случаев, когда вы можете обнаружить, что вам нужно определить второй (или вторичный) конструктор или конструкторы.

Например, представьте конструктор копирования, который копирует значения существующего экземпляра Dog в новый экземпляр Dog:

class Dog (val name: String) {
    constructor(dog: Dog) : this(dog.name)
}

val dog01 = Dog(name = "Sparky")
val dog02 = Dog(dog01)

Несколько замечаний по второстепенным конструкторам:

  • Они должны начинаться с ключевого слова конструктор.
  • Они должны вызывать первичный конструктор прямо или косвенно через другой вторичный конструктор. Вызов основного конструктора выполняется с помощью ключевого слова this, как показано в примере выше.
  • Блоки инициализатора всегда будут выполняться до тела любых вторичных конструкторов.
  • Параметры, указанные во вторичных конструкторах, не становятся атрибутами или полями класса. Они не могут иметь префикса var или val. Другими словами, вам нужно будет присвоить значения, переданные в поля, или что-то сделать с ними в теле вторичного конструктора (ов).

Заключение
Если вы работаете с Java, вы можете обнаружить, что конструкторы в Kotlin поначалу могут показаться немного сложными. Надеюсь, эта статья поможет вам в этом процессе обучения. Как всегда, просмотрите официальную документацию Kotlin и практикуйтесь, практикуйтесь, практикуйтесь, если хотите узнать больше.

Удачного кодирования!
Томас Сандерленд