Классы
Прежде чем создавать класс, подумайте, не подойдёт ли класс данных, если задача состоит в хранении данных. Также можно рассмотреть вариант с расширением существующего класса вместо создания нового с нуля.
Как и другие объектно-ориентированные языки, Kotlin использует классы для инкапсуляции данных (свойств) и поведения (функций) в переиспользуемый структурированный код.
Классы — это схемы или шаблоны для объектов, которые создаются с помощью конструкторов. Когда вы создаёте экземпляр класса, вы создаёте конкретный объект на основе такой схемы.
Kotlin предоставляет лаконичный синтаксис объявления классов. Чтобы объявить класс, используйте ключевое слово class,
после которого указывается имя класса:
class Person { /*...*/ }
- заголовка класса, который включает, в частности:
- ключевое слово
class; - имя класса;
- параметры типа, если они есть;
- основной конструктор, если он объявлен;
- ключевое слово
- тела класса (необязательно), заключённого в фигурные скобки
{}и включающего члены класса, например:
Заголовок и тело класса могут быть минимальными. Если у класса нет тела, фигурные скобки {} можно опустить:
// Класс с основным конструктором, но без тела
class Person(val name: String, var age: Int)
Ниже пример класса с заголовком и телом, после чего создаётся его экземпляр:
// Класс Person с основным конструктором,
// который инициализирует свойство name
class Person(val name: String) {
// Тело класса со свойством age
var age: Int = 0
}
fun main() {
// Создаёт экземпляр класса Person вызовом конструктора
val person = Person("Alice")
// Обращается к свойствам экземпляра
println(person.name)
// Alice
println(person.age)
// 0
}
Создание экземпляров
Экземпляр создаётся, когда вы используете класс как схему для построения реального объекта, с которым будет работать программа.
Чтобы создать экземпляр класса, укажите имя класса и круглые скобки () — почти как при вызове функции:
// Создаёт экземпляр класса Person
val anonymousUser = Person()
В Kotlin экземпляры можно создавать:
- без аргументов (
Person()): создаётся экземпляр со значениями по умолчанию, если они объявлены в классе; - с аргументами (
Person(value)): создаётся экземпляр с переданными конкретными значениями.
Созданный экземпляр можно присвоить изменяемой (var) или неизменяемой (val) переменной:
// Создаёт экземпляр со значением по умолчанию
// и присваивает его изменяемой переменной
var anonymousUser = Person()
// Создаёт экземпляр с конкретным значением
// и присваивает его неизменяемой переменной
val namedUser = Person("Joe")
Экземпляры можно создавать там, где они нужны: внутри функции main(),
внутри других функций или внутри другого класса. Также можно создать экземпляр внутри другой функции и вызвать эту функцию из main().
Следующий код объявляет класс Person со свойством для хранения имени. В нём также показано, как создать экземпляр
со значением конструктора по умолчанию и с конкретным значением:
// Заголовок класса с основным конструктором,
// который инициализирует name значением по умолчанию
class Person(val name: String = "Sebastian")
fun main() {
// Создаёт экземпляр со значением конструктора по умолчанию
val anonymousUser = Person()
// Создаёт экземпляр с конкретным значением
val namedUser = Person("Joe")
// Обращается к свойству name у экземпляров
println(anonymousUser.name)
// Sebastian
println(namedUser.name)
// Joe
}
В Kotlin, в отличие от некоторых других объектно-ориентированных языков программирования, при создании экземпляров классов не нужно ключевое слово
new.
Информацию о создании экземпляров вложенных, внутренних и анонимных внутренних классов см. в разделе Вложенные классы.
Конструкторы и блоки инициализации
При создании экземпляра класса вызывается один из его конструкторов. Класс в Kotlin может иметь основной конструктор и один или несколько дополнительных конструкторов.
Основной конструктор — главный способ инициализировать класс. Он объявляется в заголовке класса. Дополнительный конструктор предоставляет дополнительную логику инициализации и объявляется в теле класса.
И основной, и дополнительные конструкторы необязательны, но у класса должен быть хотя бы один конструктор.
Основной конструктор
Основной конструктор задаёт начальное состояние экземпляра при его создании.
Чтобы объявить основной конструктор, поместите его в заголовок класса после имени класса:
class Person constructor(name: String) { /*...*/ }
Если у основного конструктора нет аннотаций или модификаторов видимости,
ключевое слово constructor можно опустить:
class Person(name: String) { /*...*/ }
Основной конструктор может объявлять параметры как свойства. Используйте ключевое слово val перед именем аргумента,
чтобы объявить свойство только для чтения, и var, чтобы объявить изменяемое свойство:
class Person(val name: String, var age: Int) { /*...*/ }
Такие свойства-параметры конструктора хранятся как часть экземпляра и доступны извне класса.
Также можно объявлять параметры основного конструктора, которые не являются свойствами. Перед ними нет val или var,
поэтому они не сохраняются в экземпляре и доступны только внутри тела класса:
// Параметр основного конструктора, который также является свойством
class PersonWithProperty(val name: String) {
fun greet() {
println("Hello, $name")
}
}
// Только параметр основного конструктора: он не сохраняется как свойство
class PersonWithAssignment(name: String) {
// Его нужно присвоить свойству, чтобы использовать позже
val displayName: String = name
fun greet() {
println("Hello, $displayName")
}
}
Свойства, объявленные в основном конструкторе, доступны функциям-членам класса:
// Класс с основным конструктором, который объявляет свойства
class Person(val name: String, var age: Int) {
// Функция-член обращается к свойствам класса
fun introduce(): String {
return "Hi, I'm $name and I'm $age years old."
}
}
Свойствам в основном конструкторе также можно назначать значения по умолчанию:
class Person(val name: String = "John", var age: Int = 30) { /*...*/ }
Если при создании экземпляра в конструктор не передано значение, свойства используют значения по умолчанию:
// Класс с основным конструктором,
// включающим значения по умолчанию для name и age
class Person(val name: String = "John", var age: Int = 30)
fun main() {
// Создаёт экземпляр со значениями по умолчанию
val person = Person()
println("Name: ${person.name}, Age: ${person.age}")
// Name: John, Age: 30
}
Параметры основного конструктора можно использовать для инициализации дополнительных свойств класса прямо в его теле:
// Класс с основным конструктором,
// включающим значения по умолчанию для name и age
class Person(
val name: String = "John",
var age: Int = 30
) {
// Инициализирует свойство description
// из параметров основного конструктора
val description: String = "Name: $name, Age: $age"
}
fun main() {
// Создаёт экземпляр класса Person
val person = Person()
// Обращается к свойству description
println(person.description)
// Name: John, Age: 30
}
Как и в функциях, в объявлениях конструкторов можно использовать завершающие запятые:
class Person(
val name: String,
val lastName: String,
var age: Int,
) { /*...*/ }
Блоки инициализации
Основной конструктор инициализирует класс и задаёт его свойства. В большинстве случаев для этого достаточно простого кода.
Если при создании экземпляра нужно выполнить более сложные операции, поместите эту логику в блоки инициализации внутри тела класса. Такие блоки выполняются при выполнении основного конструктора.
Блоки инициализации объявляются с помощью ключевого слова init, после которого идут фигурные скобки {}.
Внутри фигурных скобок напишите любой код, который должен выполняться при инициализации:
// Класс с основным конструктором, который инициализирует name и age
class Person(val name: String, var age: Int) {
init {
// Блок инициализации выполняется при создании экземпляра
println("Person created: $name, age $age.")
}
}
fun main() {
// Создаёт экземпляр класса Person
Person("John", 30)
// Person created: John, age 30.
}
Можно добавить столько блоков инициализации (init {}), сколько нужно. Они выполняются в том порядке,
в котором указаны в теле класса, вместе с инициализаторами свойств:
// Класс с основным конструктором, который инициализирует name и age
class Person(val name: String, var age: Int) {
// Первый блок инициализации
init {
// Выполняется первым при создании экземпляра
println("Person created: $name, age $age.")
}
// Второй блок инициализации
init {
// Выполняется после первого блока инициализации
if (age < 18) {
println("$name is a minor.")
} else {
println("$name is an adult.")
}
}
}
fun main() {
// Создаёт экземпляр класса Person
Person("John", 30)
// Person created: John, age 30.
// John is an adult.
}
Параметры основного конструктора можно использовать в блоках инициализации. Например, в коде выше первый и второй
инициализаторы используют параметры name и age из основного конструктора.
Частый сценарий использования блоков init — проверка данных. Например, с помощью вызова функции
require:
class Person(val age: Int) {
init {
require(age > 0) { "age must be positive" }
}
}
Дополнительные конструкторы
В Kotlin дополнительные конструкторы — это конструкторы, которые класс может иметь помимо основного конструктора. Они полезны, когда нужно несколько способов инициализировать класс или обеспечить совместимость с Java.
Чтобы объявить дополнительный конструктор, используйте ключевое слово constructor внутри тела класса,
указав параметры конструктора в круглых скобках (). Логику конструктора добавьте в фигурных скобках {}:
// Заголовок класса с основным конструктором, который инициализирует name и age
class Person(val name: String, var age: Int) {
// Дополнительный конструктор принимает age как String
// и преобразует его в Int
constructor(name: String, age: String) : this(name, age.toIntOrNull() ?: 0) {
println("$name created with converted age: ${this.age}")
}
}
fun main() {
// Использует дополнительный конструктор с age как String
Person("Bob", "8")
// Bob created with converted age: 8
}
Выражение
age.toIntOrNull() ?: 0использует элвис-оператор. Дополнительную информацию см. в разделе Null-безопасность.
В коде выше дополнительный конструктор делегирует вызов основному конструктору через ключевое слово this,
передавая name и значение age, преобразованное в целое число.
В Kotlin дополнительные конструкторы должны делегировать вызов основному конструктору. Такая делегация гарантирует, что вся логика инициализации основного конструктора выполнится до логики любого дополнительного конструктора.
Делегация конструктора может быть:
- прямой, когда дополнительный конструктор сразу вызывает основной конструктор;
- косвенной, когда один дополнительный конструктор вызывает другой, а тот, в свою очередь, делегирует вызов основному конструктору.
Ниже пример, показывающий, как работает прямая и косвенная делегация:
// Заголовок класса с основным конструктором, который инициализирует name и age
class Person(
val name: String,
var age: Int
) {
// Дополнительный конструктор с прямой делегацией
// основному конструктору
constructor(name: String) : this(name, 0) {
println("Person created with default age: $age and name: $name.")
}
// Дополнительный конструктор с косвенной делегацией:
// this("Bob") -> constructor(name: String) -> основной конструктор
constructor() : this("Bob") {
println("New person created with default age: $age and name: $name.")
}
}
fun main() {
// Создаёт экземпляр на основе прямой делегации
Person("Alice")
// Person created with default age: 0 and name: Alice.
// Создаёт экземпляр на основе косвенной делегации
Person()
// Person created with default age: 0 and name: Bob.
// New person created with default age: 0 and name: Bob.
}
В классах с блоками инициализации (init {}) код внутри этих блоков становится частью основного конструктора.
Поскольку дополнительные конструкторы сначала делегируют вызов основному конструктору, все блоки инициализации
и инициализаторы свойств выполняются до тела дополнительного конструктора. Даже если у класса нет основного конструктора,
делегация всё равно происходит неявно:
// Заголовок класса без основного конструктора
class Person {
// Блок инициализации выполняется при создании экземпляра
init {
// Выполняется до дополнительного конструктора
println("1. First initializer block runs")
}
// Дополнительный конструктор, принимающий целочисленный параметр
constructor(i: Int) {
// Выполняется после блока инициализации
println("2. Person $i is created")
}
}
fun main() {
// Создаёт экземпляр класса Person
Person(1)
// 1. First initializer block runs
// 2. Person 1 created
}
Классы без конструкторов
Классы, в которых не объявлены конструкторы (ни основной, ни дополнительные), имеют неявный основной конструктор без параметров:
// Класс без явно объявленных конструкторов
class Person {
// Основной и дополнительные конструкторы не объявлены
}
fun main() {
// Создаёт экземпляр класса Person
// с помощью неявного основного конструктора
val person = Person()
}
Видимость такого неявного основного конструктора — public, то есть он доступен отовсюду. Если вы не хотите,
чтобы у класса был открытый конструктор, объявите пустой основной конструктор с нестандартной видимостью:
class Person private constructor() { /*...*/ }
На JVM, если все параметры основного конструктора имеют значения по умолчанию, компилятор неявно предоставляет конструктор без параметров, который использует эти значения.
Это упрощает использование Kotlin с библиотеками вроде Jackson или Spring Data JPA, которые создают экземпляры классов через конструкторы без параметров.
В следующем примере Kotlin неявно предоставляет конструктор без параметров
Person(), который использует значение по умолчанию"":> class Person(val personName: String = "") > ``` <a name="inheritance"></a> <!-- ## Inheritance --> ## Наследование <!-- Class inheritance in Kotlin allows you to create a new class (derived class) from an existing class (base class), inheriting its properties and functions while adding or modifying behavior. --> Наследование классов в Kotlin позволяет создавать новый класс (производный класс) на основе существующего класса (базового класса), наследуя его свойства и функции, а также добавляя или изменяя поведение. <!-- For detailed information about inheritance hierarchies and how to use of the `open` keyword, see the Inheritance section. --> Подробную информацию об иерархиях наследования и использовании ключевого слова `open` см. в разделе [Наследование](inheritance.html). <a name="abstract-classes"></a> <!-- ## Abstract classes --> ## Абстрактные классы <!-- In Kotlin, abstract classes are classes that can't be instantiated directly. They are designed to be inherited by other classes which define their actual behavior. This behavior is called an implementation. --> В Kotlin абстрактные классы — это классы, экземпляры которых нельзя создать напрямую. Они предназначены для наследования другими классами, которые определяют их фактическое поведение. Такое поведение называется *реализацией*. <!-- An abstract class can declare abstract properties and functions, which must be implemented by subclasses. --> Абстрактный класс может объявлять абстрактные свойства и функции, которые должны быть реализованы в подклассах. <!-- Abstract classes can also have constructors. These constructors initialize class properties and enforce required parameters for subclasses. Declare an abstract class using the `abstract` keyword: --> У абстрактных классов также могут быть конструкторы. Эти конструкторы инициализируют свойства класса и задают обязательные параметры для подклассов. Абстрактный класс объявляется с помощью ключевого слова `abstract`: ```kotlin abstract class Person(val name: String, val age: Int)
У абстрактного класса могут быть как абстрактные, так и неабстрактные члены (свойства и функции).
Чтобы объявить член абстрактным, нужно явно использовать ключевое слово abstract.
Абстрактные классы и функции не нужно помечать ключевым словом open, потому что они по умолчанию доступны для наследования.
Подробнее о ключевом слове open см. в разделе Наследование.
Абстрактные члены не имеют реализации в абстрактном классе. Реализация определяется в подклассе или наследующем классе
с помощью функции или свойства с модификатором override:
// Абстрактный класс с основным конструктором,
// который объявляет name и age
abstract class Person(
val name: String,
val age: Int
) {
// Абстрактный член:
// не предоставляет реализацию
// и должен быть реализован в подклассах
abstract fun introduce()
// Неабстрактный член: имеет реализацию
fun greet() {
println("Hello, my name is $name.")
}
}
// Подкласс, который предоставляет реализацию абстрактного члена
class Student(
name: String,
age: Int,
val school: String
) : Person(name, age) {
override fun introduce() {
println("I am $name, $age years old, and I study at $school.")
}
}
fun main() {
// Создаёт экземпляр класса Student
val student = Student("Alice", 20, "Engineering University")
// Вызывает неабстрактный член
student.greet()
// Hello, my name is Alice.
// Вызывает переопределённый абстрактный член
student.introduce()
// I am Alice, 20 years old, and I study at Engineering University.
}
Вспомогательные объекты
В Kotlin у каждого класса может быть вспомогательный объект. Вспомогательные объекты — это разновидность объявления объекта, которая позволяет обращаться к его членам по имени класса без создания экземпляра класса.
Предположим, вам нужно написать функцию, которую можно вызвать без создания экземпляра класса, но которая логически связана с классом, например фабричную функцию. В таком случае её можно объявить внутри вспомогательного объявления объекта в классе:
// Класс с основным конструктором, который объявляет свойство name
class Person(
val name: String
) {
// Тело класса со вспомогательным объектом
companion object {
fun createAnonymous() = Person("Anonymous")
}
}
fun main() {
// Вызывает функцию без создания экземпляра класса
val anonymous = Person.createAnonymous()
println(anonymous.name)
// Anonymous
}
Если вы объявляете вспомогательный объект внутри класса, к его членам можно обращаться, используя только имя класса как квалификатор.
Дополнительную информацию см. в разделе Вспомогательные объекты.