Kotlin. Основной и вторичный конструкторы. Init блок.
Прежде чем разбираться в том, какие бывают конструкторы, нужно понять, что такое конструктор и в чём его предназначение. А для того, чтобы это понять нужно разобраться с другим определением, которое будет довольно часто встречаться и в документации Kotlin, и в различных статьях (в том числе моих). Речь идёт о свойствах.
Говоря простым языком, свойства — это те вещи, которые помогают идентифицировать класс. С их помощью класс описывается и ему задаются какие-либо параметры и значения.
Допустим, у нас есть класс Person . В качестве свойств у такого класса могут быть имя и возраст.
// класс Person со свойствами name и age class Person()
Свойств у класса может быть столько, сколько ему нужно. Но все они должны быть инициализированы при создании экземпляра этого класса. Поэтому для удобства был придуман конструктор — это специальный блок кода, который вызывается при создании экземпляра класса. Ему передаются необходимые значения, которые потом используются для инициализации свойств.
В Kotlin конструкторы бывают двух видов: основной и вторичный. У класса может и не быть конструктора, но Kotlin всё равно автоматически сгенерирует основной конструктор по умолчанию (без параметров).
Основной конструктор (primary constructor)
Также известен как первичный, главный, primary конструктор.
Объявляется он сразу после имени класса и состоит из ключевого слова constructor и круглых скобок.
class Person constructor(name: String, age: Int)
Если в классе нет иной логики, то фигурные скобки можно опустить.
class Person constructor(name: String, age: Int)
Можно обойтись и без ключевого слова constructor при условии, что нет аннотаций или модификаторов доступа.
class Person(name: String, age: Int)
Параметры, переданные в конструктор, можно использовать для инициализации свойств, объявленных в теле класса.
class Person(name: String, age: Int)
А можно упростить еще больше и из параметров конструктора сделать свойства класса. Для этого перед именем параметра нужно указать ключевое слова val (свойство только для чтения) или var (свойство для чтения и редактирования).
class Person(val name: String, var age: Int)
При этом любому из свойств можно присвоить значение по умолчанию. Тогда при создании экземпляра класса для этого свойства значение можно либо не указывать, либо указать, если оно отличается от стандартного.
class Person(val name: String, var age: Int = 30) . val adam = Person("Adam") val alice = Person("Alice", 25) println("$, $") // Adam, 30 println("$, $") // Alice, 25
Также из примера видно, что при создании нового экземпляра класса не нужно указывать слово new (как в Java). Достаточно вызвать его конструктор (даже если он пустой).
Если у всех параметров конструктора есть значение по умолчанию, то будет автоматически сгенерирован дополнительный конструктор без параметров, использующий значения по умолчанию. Это делается для того, чтобы упростить работу с библиотеками, которые для создания экземпляра класса используют конструктор без параметров.
У класса может быть суперкласс. Тогда его основной конструктор должен инициализировать свойства, унаследованные от суперкласса.
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
Конструктор можно сделать приватным. Тогда никто и ничто не сможет создать экземпляр этого класса.
class Person private constructor(val name: String, var age: Int) . val adam = Person("Adam", 30) // вылетит ошибка
Вторичный конструктор (secondary constructor)
Также известен как вспомогательный, дополнительный, secondary конструктор.
Вторичный конструктор используется в том случае, когда необходимо определить альтернативный способ создания класса. В Kotlin это применяется редко, так как обычно основного конструктора бывает достаточно благодаря возможности добавлять значения по умолчанию и использовать именованные аргументы.
Объявляется вторичный конструктор внутри тела класса при помощи ключевого слова constructor .
При этом если у класса есть основной конструктор, то все вторичные конструкторы обязательно должны явно или косвенно его вызывать. Подразумевается, что либо вторичный конструктор сам вызывает основной конструктор, либо сначала вызывает другой вторичный конструктор, который в свою очередь обращается к основному конструктору. Обращение к основному конструктору осуществляется при помощи ключевого слова this .
class Person(val name: String, var age: Int) < constructor(name: String, age: Int, id: Int): this(name, age) < . >>
Если основного конструктора нет, то и обращаться к нему не надо.
Во вторичном конструкторе нельзя объявлять свойства класса. Все передаваемые ему параметры можно использовать либо для передачи основному конструктору, либо для инициализации свойств, объявленных в теле класса.
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 >>
Также во вторичный конструктор можно добавить какую-либо логику.
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 .
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 < . >// и т.д. >
Блок инициализации может быть добавлен, даже если у класса нет основного конструктора. В этом случае его код будет выпонен раньше кода вторичных конструкторов.
Classes
Classes in Kotlin are declared using the keyword class :
The class declaration consists of the class name, the class header (specifying its type parameters, the primary constructor, and some other things), and the class body surrounded by curly braces. Both the header and the body are optional; if the class has no body, the curly braces can be omitted.
Constructors
A class in Kotlin can have a primary constructor and one or more secondary constructors. The primary constructor is a part of the class header, and it goes after the class name and optional type parameters.
If the primary constructor does not have any annotations or visibility modifiers, the constructor keyword can be omitted:
The primary constructor cannot contain any code. Initialization code can be placed in initializer blocks prefixed with the init keyword.
During the initialization of an instance, the initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers:
Primary constructor parameters can be used in the initializer blocks. They can also be used in property initializers declared in the class body:
Kotlin has a concise syntax for declaring properties and initializing them from the primary constructor:
Such declarations can also include default values of the class properties:
You can use a trailing comma when you declare class properties:
Much like regular properties, properties declared in the primary constructor can be mutable ( var ) or read-only ( val ).
If the constructor has annotations or visibility modifiers, the constructor keyword is required and the modifiers go before it:
Secondary constructors
A class can also declare secondary constructors, which are prefixed with constructor :
If the class has a primary constructor, each secondary constructor needs to delegate to the primary constructor, either directly or indirectly through another secondary constructor(s). Delegation to another constructor of the same class is done using the this keyword:
class Person(val name: String) < val children: MutableList
Code in initializer blocks effectively becomes part of the primary constructor. Delegation to the primary constructor happens at the moment of access to the first statement of a secondary constructor, so the code in all initializer blocks and property initializers is executed before the body of the secondary constructor.
Even if the class has no primary constructor, the delegation still happens implicitly, and the initializer blocks are still executed:
If a non-abstract class does not declare any constructors (primary or secondary), it will have a generated primary constructor with no arguments. The visibility of the constructor will be public.
If you don’t want your class to have a public constructor, declare an empty primary constructor with non-default visibility:
On the JVM, if all of the primary constructor parameters have default values, the compiler will generate an additional parameterless constructor which will use the default values. This makes it easier to use Kotlin with libraries such as Jackson or JPA that create class instances through parameterless constructors.
Creating instances of classes
To create an instance of a class, call the constructor as if it were a regular function:
Kotlin does not have a new keyword.
The process of creating instances of nested, inner, and anonymous inner classes is described in Nested classes.
Class members
Inheritance
Classes can be derived from each other and form inheritance hierarchies. Learn more about inheritance in Kotlin.
Abstract classes
A class may be declared abstract , along with some or all of its members. An abstract member does not have an implementation in its class. You don’t need to annotate abstract classes or functions with open .
You can override a non-abstract open member with an abstract one.
Companion objects
If you need to write a function that can be called without having a class instance but that needs access to the internals of a class (such as a factory method), you can write it as a member of an object declaration inside that class.
Even more specifically, if you declare a companion object inside your class, you can access its members using only the class name as a qualifier.