Конструкторы

Последнее обновление: 26.03.2024

Для создания объекта необходимо вызвать конструктор класса. По умолчанию компилятор создает конструктор, который не принимает параметров и который мы можем использовать. Но также мы можем определять свои собственные конструкторы. Классы в Kotlin могут иметь один первичный конструктор (primary constructor) и один или несколько вторичных конструкторов (secondary constructor).

Вторичные конструкторы

Вторичные конструкторы определяются в теле класса с помощью ключевого слова constructor:

fun main() {
    
    val tom = Person("Tom", 39)
    val bob = Person("Bob", 45)
     
    println("Name: ${tom.name}  Age: ${tom.age}")
    println("Name: ${bob.name}  Age: ${bob.age}")
}

class Person{
    val name: String
    var age: Int
     
    constructor(_name: String, _age: Int){
        name = _name
        age = _age
    }
}

Конструкторы во многом похожи на функциии, как и обычные функции, могут иметь параметры. Здесь в классе Person определен конструктор, принимает два параметра: _name и _age - условно говоря имя и возраст человека. Внутри конструктора эти значения передаются переменным name и age:

constructor(_name: String, _age: Int){
    name = _name
    age = _age
}

В функции main создаются два объекта Person. Для их создания применяется вторичный конструктор, параметрам которого передаются некоторые значения:

val tom = Person("Tom", 39)
val bob = Person("Bob", 45)

Консольный вывод программы:

Name: Tom  Age: 39
Name: Bob  Age: 45

Аналогичным образом мы можем определять и большее количество конструкторов:

fun main() {
    
    val tom = Person("Tom")
    val bob = Person("Bob", 45)
     
    println("Name: ${tom.name}  Age: ${tom.age}")
    println("Name: ${bob.name}  Age: ${bob.age}")
}

class Person{
    val name: String
    var age: Int = 0
    
    constructor(_name: String){
        name = _name
    }
    constructor(_name: String, _age: Int){
        name = _name
        age = _age
    }
}

Теперь в классе Person также определен еще один конструктор, который принимает только один параметр - имя человека и передает его значение переменной name. Переменная age в данном случае оставляет значение по умолчанию - число 0.

В функции main вызываем оба конструктора:

val tom = Person("Tom")
val bob = Person("Bob", 45)

Для создания первого объекта вызывается первый конструктор, а для второго - второй конструктор. Консольный вывод:

Name: Tom  Age: 0
Name: Bob  Age: 45

Однако в данном случае мы сталкиваемся с дублированием кода - установкой переменной name. В данном случае это может быть не актуально. Но что, если мы захотим добавить какую-то более сложную логику установки имени, например, с проверкой на длину строки, какие-то другие маркеры? В этом случае лучше делегировать во втором конструкторе установку имени в первый конструктор. То есть из второго конструктора вызвать первый. Для этого применяется ключевое слово this:

class Person{
    val name: String
    var age: Int = 0
    
    constructor(_name: String){
        name = _name
    }
    constructor(_name: String, _age: Int): this(_name){
        age = _age
    }
}

Здесь выражение this(_name) как раз и представляет вызов первого конструктора, где и устанавливается имя.

Первичный конструктор

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

class Person constructor(_name: String, _age: Int){
   
}

Конструкторы, как и обычные функции, могут иметь параметры. Так, в данном случае конструктор имеет параметр _name, который представляет тип String. Через параметры конструктора мы можем передать извне данные и использовать их для инициализации объекта. При этом первичный конструктор в отличие от функций не определяет никаких действий, он только может принимать данные извне через параметры.

Если первичный конструктор не имеет никаких аннотаций или модификаторов доступа, как в данном случае, то ключевое слово constructor можно опустить:

class Person (_name: String, _age: Int){
   
}

Значения параметров первичного конструктора можно использовать внутри класса, например, передать их значения переменным класса:

fun main() {
    
    val tom = Person("Tom", 39)
    val bob = Person("Bob", 45)
     
    println("Name: ${tom.name}  Age: ${tom.age}")
    println("Name: ${bob.name}  Age: ${bob.age}")
}

class Person (_name: String, _age: Int){
    val name: String = _name
    var age: Int = _age
}

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

class Person(_name: String){
    val name: String = _name
    var age: Int = 0
	
    constructor(_name: String, _age: Int) : this(_name){
        age = _age
    }
}

Здесь в классе Person определен первичный конструктор, который принимает значение для установки свойства name:

class Person(_name: String)

И также добавлен вторичный конструктор. Он принимает два параметра: _name и _age. С помощью ключевого слова this вызывается первичный конструктор, поэтому через этот вызов необходимо передать значения для параметров первичного конструктора. В частности, в первичный конструктор передается значение параметра _name. В самом вторичном конструкторе устанавливается значение свойства age.

constructor(_name: String, _age: Int) : this(_name){
	age = _age
}

Таким образом, при вызове вторичного конструктора вначале вызывается первичный конструктор, срабатывает блок инициализатора, который устанавливает свойство name. Затем выполняются собственно действия вторичного конструктора, который устанавливает свойство age.

Используем данную модификацию класса Person:

fun main() {

    val tom = Person("Tom")         // обращение к первичному конструктору
    val bob = Person("Bob", 45)     // обращение к вторичному конструктору
    
    println("Name: ${tom.name}  Age: ${tom.age}")
    println("Name: ${bob.name}  Age: ${bob.age}")
}

class Person(_name: String){
    val name: String = _name
    var age: Int = 0
	
    constructor(_name: String, _age: Int) : this(_name){
        age = _age
    }
}

В функции main создаются два объекта Person. Для создания объекта tom применяется первичный конструктор, который принимает один параметр. Для создания объекта bob применяется вторичный конструктор с двумя параметрами.

Консольный вывод программы:

Name: Tom  Age: 0
Name: Bob  Age: 45

Первичный конструктор и свойства

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

fun main() {

    val bob = Person("Bob", 23)

    println("Name: ${bob.name}  Age: ${bob.age}")
}

class Person(val name: String, var age: Int)    // { } - здесь не нужны

Свойства определяются как и параметры, при этом их определение начинается с ключевого слова val (если их не планируется изменять) и var (если свойства должны быть изменяемыми). И в этом случае нам уже необязательно явным образом определять эти свойства в теле класса, так как их уже определяет конструктор. И при вызове конструктора этим свойствам автоматически передаются значения: Person("Bob", 23)

Обратите внимание, что если в классе определен только первичный конструктор, то фигурные скобки, которые оформляют тело класса, использовать необязательно. Хотя мы могли бы написать и так:

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

Инициализатор

Кроме конструкторов для инициализации объектов мы можем использовать блоки инициализаторов. Они представляют блок кода в фигурных скобках, перед которым идет слово init :

fun main() {
    
    val tom = Person("Tom", -100)
    val bob = Person("Bob", 45)
     
    println("Name: ${tom.name}  Age: ${tom.age}")
    println("Name: ${bob.name}  Age: ${bob.age}")
}

class Person (_name: String, _age: Int){
    val name: String
    var age: Int = 1

    init{
        name = _name
        if(_age >0 && _age < 110) age = _age
    }
}

Здесь в классе Person определен следующий блок инициализатора:

init{
        name = _name
        if(_age >0 && _age < 110) age = _age
    }

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

Стоит отметить, что в классе может быть определено одновременно несколько блоков инициализатора.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850