Статьи Kotlin. Основной и вторичный конструкторы. Init блок.
Post
Cancel

Kotlin. Основной и вторичный конструкторы. Init блок.

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

Говоря простым языком, свойства - это те вещи, которые помогают идентифицировать класс. С их помощью класс описывается и ему задаются какие-либо параметры и значения.

Допустим, у нас есть класс Person. В качестве свойств у такого класса могут быть имя и возраст.

1
2
3
4
5
// класс Person со свойствами name и age
class Person() {
  val name
  val age
}

Свойств у класса может быть столько, сколько ему нужно. Но все они должны быть инициализированы при создании экземпляра этого класса. Поэтому для удобства был придуман конструктор - это специальный блок кода, который вызывается при создании экземпляра класса. Ему передаются необходимые значения, которые потом используются для инициализации свойств.

В Kotlin конструкторы бывают двух видов: основной и вторичный. У класса может и не быть конструктора, но Kotlin всё равно автоматически сгенерирует основной конструктор по умолчанию (без параметров).


Основной конструктор (primary constructor)

Также известен как первичный, главный, primary конструктор.

Объявляется он сразу после имени класса и состоит из ключевого слова constructor и круглых скобок.

1
2
3
class Person constructor(name: String, age: Int) {
  ...
}

Если в классе нет иной логики, то фигурные скобки можно опустить.

1
class Person constructor(name: String, age: Int)

Можно обойтись и без ключевого слова constructor при условии, что нет аннотаций или модификаторов доступа.

1
class Person(name: String, age: Int)

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

1
2
3
4
class Person(name: String, age: Int) {
  val name = name
  var age = age
}

А можно упростить еще больше и из параметров конструктора сделать свойства класса. Для этого перед именем параметра нужно указать ключевое слова val (свойство только для чтения) или var (свойство для чтения и редактирования).

1
class Person(val name: String, var age: Int)

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

1
2
3
4
5
6
7
8
class Person(val name: String, var age: Int = 30)

...

val adam = Person("Adam")
val alice = Person("Alice", 25)
println("${adam.name}, ${adam.age}")      // Adam, 30
println("${alice.name}, ${alice.age}")    // Alice, 25

Также из примера видно, что при создании нового экземпляра класса не нужно указывать слово new (как в Java). Достаточно вызвать его конструктор (даже если он пустой).

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

У класса может быть суперкласс. Тогда его основной конструктор должен инициализировать свойства, унаследованные от суперкласса.

1
2
3
4
5
6
7
8
open class Base(p: Int)

class Person(val name: String, var age: Int = 30, val p: Int) : Base(p)

...

val adam = Person("Adam", 30, 1000)
println(adam.p)                      // 1000

Конструктор можно сделать приватным. Тогда никто и ничто не сможет создать экземпляр этого класса.

1
2
3
4
class Person private constructor(val name: String, var age: Int)
...

val adam = Person("Adam", 30)  // вылетит ошибка

Вторичный конструктор (secondary constructor)

Также известен как вспомогательный, дополнительный, secondary конструктор.

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

Объявляется вторичный конструктор внутри тела класса при помощи ключевого слова constructor.

1
2
3
4
5
class Person {
  constructor(id: Int) {
    ...
  }
}

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

1
2
3
4
5
6
class Person(val name: String, var age: Int) {

  	constructor(name: String, age: Int, id: Int): this(name, age) {
      ...
  	}
}

Если основного конструктора нет, то и обращаться к нему не надо.

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

1
2
3
4
5
6
7
class Person(val name: String, var age: Int) {
    var id: Int = 0

  	constructor(name: String, age: Int, id: Int): this(name, age) {
      this.id= id
  	}
}

Также во вторичный конструктор можно добавить какую-либо логику.

1
2
3
4
5
6
7
class Person(val name: String, var age: Int) {
    var id: Int = 0

  	constructor(name: String, age: Int, id: Int): this(name, age) {
      if(id > 0) this.id = id * 2
  	}
}

Если у класса есть суперкласс, но нет основного конструктора, то каждый вторичный конструктор должен обращаться к конструктору суперкласса при помощи ключевого слова super.

1
2
3
4
5
6
7
8
9
10
open class Base(val p: Int)

class Person : Base {
    constructor(name: String, age: Int, p: Int) : super(p)
}

...

val adam = Person("Adam", 30, 1)
println(adam.p)                   // 1

Блок инициализации (init блок)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person(val name: String, var age: Int) {
    var id: Int = 0

    // require выдает ошибку с указанным текстом, если условие в левой части false
    init {
      require(name.isNotBlank(), {"У человека должно быть имя!"})
      require(age > -1, {"Возраст не может быть отрицательным."})
    }

  	constructor(name: String, age: Int, id: Int): this(name, age) {
      if(id > 0) this.id = id * 2
  	}
}

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

Код в блоке инициализации выполняется сразу после создания экземпляра класса, т.е. сразу после вызова основного конструктора.

В классе может быть один или несколько блоков инициализации и выполняться они будут последовательно.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person(val name: String, var age: Int) {
    // сначала вызывается основной конструктор и создаются свойства класса
    // далее вызывается первый блок инициализации
    init {
      ...
    }

    // после первого вызывается второй блок инициализации
    init {
      ...
    }

    // и т.д.
}

Блок инициализации может быть добавлен, даже если у класса нет основного конструктора. В этом случае его код будет выпонен раньше кода вторичных конструкторов.


Последовательность создания объекта

call-sequence


Полезные ссылки

Constructors - официальная документация.
Конструкторы - перевод на русский.
Kotlin: копаем глубже. Конструкторы и инициализаторы - разбор на хабре.

This post is licensed under CC BY 4.0 by the author.

Kotlin. Классы перечислений (enum).

Activity (Активность, Операция)