Kotlin guide done. (20.10.26)

This commit is contained in:
2026-10-20 12:54:25 +03:00
parent da3ad9dd1a
commit 6853ada348
81 changed files with 30112 additions and 0 deletions
@@ -0,0 +1,326 @@
---
title: Классы и конструкторы
description: Основной конструктор, параметры-свойства, вторичные конструкторы и фабрики
tags:
- kotlin
- oop
---
Минимальный класс - одна строка:
```kotlin
class Empty
fun main() {
val e = Empty() // new не нужен
println(e::class.simpleName) // Empty
}
```
Оператора `new` в Kotlin нет: конструктор вызывается как функция.
## Основной конструктор
Объявляется в заголовке класса:
```kotlin
class User(name: String, age: Int) {
val displayName = name.uppercase() // параметры доступны в теле
val isAdult = age >= 18
}
fun main() {
val u = User("иван", 20)
println("${u.displayName} ${u.isAdult}") // ИВАН true
}
```
Параметры `name` и `age` - **только параметры**: они видны в инициализаторах свойств и в блоках `init`, но не становятся членами класса.
```kotlin
class User(name: String) {
val displayName = name.uppercase()
// fun show() = name // ошибка: name недоступен в методах
}
fun main() { println(User("иван").displayName) } // ИВАН
```
## Параметры-свойства
Добавьте `val` или `var` - параметр станет свойством:
```kotlin
class User(val name: String, var age: Int) {
fun show() = "$name, $age" // теперь доступны везде
}
fun main() {
val u = User("Иван", 20)
u.age = 21
println(u.show()) // Иван, 21
}
```
Это самая частая форма: одна строка вместо поля, конструктора, геттера и сеттера.
Можно смешивать: часть параметров сделать свойствами, часть оставить аргументами:
```kotlin
class User(val name: String, password: String) {
private val hash = password.hashCode() // пароль не хранится как есть
fun check(input: String) = input.hashCode() == hash
}
fun main() {
val u = User("Иван", "secret")
println(u.check("secret")) // true
println(u.check("wrong")) // false
}
```
## Значения по умолчанию
```kotlin
class Server(
val host: String = "localhost",
val port: Int = 8080,
val secure: Boolean = false,
)
fun main() {
println(Server().port) // 8080
println(Server("example.com", 443, true).host) // example.com
println(Server(port = 9090).port) // 9090 — именованный аргумент
}
```
Это заменяет телескопические конструкторы и во многих случаях - паттерн Builder. Подробнее: [Функции](../Синтаксис%20и%20типы/Работа%20с%20данными/Функции.md)
Висячая запятая после последнего параметра разрешена - упрощает правки и diff.
## Модификаторы и аннотации
Если у конструктора есть модификатор видимости или аннотация, слово `constructor` обязательно:
```kotlin
class Service private constructor(val id: Int) {
companion object {
fun create(id: Int) = if (id > 0) Service(id) else null
}
}
fun main() {
println(Service.create(1)?.id) // 1
println(Service.create(-1)) // null
// Service(1) // ошибка: конструктор приватный
}
```
Приватный конструктор + фабрика в компаньоне - способ контролировать создание: валидация, кеширование, возврат `null` вместо исключения.
## Вторичные конструкторы
Объявляются в теле класса словом `constructor`:
```kotlin
class Point(val x: Int, val y: Int) {
constructor(value: Int) : this(value, value) // точка на диагонали
constructor() : this(0, 0)
override fun toString() = "($x, $y)"
}
fun main() {
println(Point(1, 2)) // (1, 2)
println(Point(5)) // (5, 5)
println(Point()) // (0, 0)
}
```
Вторичный конструктор **обязан** делегировать основному через `this(...)` - напрямую инициализировать свойства он не может.
Класс без основного конструктора может иметь только вторичные:
```kotlin
class Config4 {
val source: String
constructor(file: java.io.File) {
source = "файл ${file.name}"
}
constructor(url: String, timeout: Int) {
source = "$url (таймаут $timeout)"
}
}
fun main() {
println(Config4("http://x.ru", 30).source) // http://x.ru (таймаут 30)
}
```
Здесь делегировать некому, поэтому свойство инициализируется в каждом конструкторе. Форма редкая - обычно хватает основного конструктора со значениями по умолчанию.
### Когда нужны вторичные
Почти никогда. Значения по умолчанию покрывают большинство случаев:
```kotlin
// вместо трёх конструкторов
class Point(val x: Int = 0, val y: Int = x)
fun main() {
println("${Point().x} ${Point(5).y} ${Point(1, 2).y}") // 0 5 2
}
```
Вторичный конструктор оправдан, когда нужно **преобразовать** аргументы другого типа или выполнить работу перед делегированием:
```kotlin
class Duration2(val millis: Long) {
constructor(seconds: Int) : this(seconds * 1000L)
constructor(text: String) : this(text.removeSuffix("s").toLong() * 1000)
}
fun main() {
println(Duration2(2).millis) // 2000
println(Duration2("3s").millis) // 3000
}
```
Но и здесь фабричные функции в компаньоне обычно читаются лучше - у них есть имена:
```kotlin
class Duration3 private constructor(val millis: Long) {
companion object {
fun ofSeconds(s: Int) = Duration3(s * 1000L)
fun parse(text: String) = Duration3(text.removeSuffix("s").toLong() * 1000)
}
}
fun main() {
println(Duration3.ofSeconds(2).millis) // 2000
println(Duration3.parse("3s").millis) // 3000
}
```
Два конструктора с одинаковыми типами параметров объявить нельзя, а фабрик с разными именами - сколько угодно.
## Валидация в конструкторе
```kotlin
class Person(val name: String, val age: Int) {
init {
require(name.isNotBlank()) { "имя не может быть пустым" }
require(age in 0..150) { "недопустимый возраст: $age" }
}
}
fun main() {
println(Person("Иван", 20).name) // Иван
try {
Person("", 20)
} catch (e: IllegalArgumentException) {
println(e.message) // имя не может быть пустым
}
}
```
Блок `init` выполняется при создании объекта. Подробнее о порядке: [Инициализация](Инициализация.md)
## Создание объекта
```kotlin
class User(val name: String)
fun main() {
val u = User("Иван") // просто вызов
val list = List(3) { User("u$it") } // в лямбде
val byRef = ::User // ссылка на конструктор
println("${u.name} ${list.size} ${byRef("Пётр").name}") // Иван 3 Пётр
}
```
`::User` - ссылка на конструктор, её можно передать как функцию. Подробнее: [Лямбды и функциональные типы](../Синтаксис%20и%20типы/Работа%20с%20данными/Лямбды%20и%20функциональные%20типы.md)
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `new User("Иван")` | `User("Иван")` |
> | конструктор = метод с телом | основной конструктор в заголовке |
> | поле + присваивание в конструкторе | `val` в параметрах конструктора |
> | телескопические конструкторы | значения по умолчанию |
> | Builder для многих параметров | именованные аргументы |
> | `this(...)` для делегирования | то же |
> | `private` конструктор + `static` фабрика | `private constructor` + `companion object` |
>
> **Главная экономия - параметры-свойства.** В Java класс из четырёх полей занимает 15+ строк:
> ```java
> public class User {
> private final String name;
> private final int age;
> public User(String name, int age) {
> this.name = name;
> this.age = age;
> }
> public String getName() { return name; }
> public int getAge() { return age; }
> }
> ```
> В Kotlin это `class User(val name: String, val age: Int)`.
>
> **Builder обычно не нужен.** В Java паттерн Builder решает проблему конструктора с десятком опциональных параметров. Именованные аргументы и значения по умолчанию дают то же самое без дополнительного класса:
> ```kotlin
> class Request(
> val url: String,
> val method: String = "GET",
> val timeout: Int = 30,
> val retries: Int = 0,
> )
>
> fun main() {
> println(Request("http://x.ru", timeout = 60).timeout) // 60
> }
> ```
> Builder остаётся полезен, когда объект собирается по частям в разных местах кода.
>
> Сравнить: [Конструкторы в Java](../../Программирование%20на%20Java/ООП/Конструкторы.md), [Builder](../../Программирование%20на%20Java/Паттерны%20проектирования/Builder.md)
> [!warning] Подводные камни
> **Параметр без `val`/`var` недоступен в методах** - только в инициализаторах и `init`. Ошибка «unresolved reference» в методе обычно означает забытый `val`.
>
> **`val` в конструкторе делает свойство частью публичного API.** Если значение нужно только внутри, пишите `private val`:
> ```kotlin
> class Service4(private val apiKey: String) {
> fun call() = "запрос с ключом длины ${apiKey.length}"
> }
>
> fun main() {
> val s = Service4("secret")
> println(s.call()) // запрос с ключом длины 6
> // println(s.apiKey) // ошибка: приватное свойство
> }
> ```
>
> **Значения по умолчанию не видны из Java.** Без `@JvmOverloads` Java-код обязан передать все аргументы:
> ```kotlin
> class Server2 @JvmOverloads constructor(
> val host: String = "localhost",
> val port: Int = 8080,
> )
>
> fun main() { println(Server2().port) } // 8080
> ```
>
> **Вторичный конструктор не может объявлять свойства:**
> ```kotlin
> class Point2(val x: Int) {
> // constructor(val y: Int) : this(y) // ошибка: val запрещён здесь
> constructor(y: Long) : this(y.toInt())
> }
>
> fun main() { println(Point2(5L).x) } // 5
> ```
>
> **Порядок инициализации не совпадает с порядком конструкторов.** Блоки `init` и инициализаторы свойств выполняются **до** тела вторичного конструктора:
> ```kotlin
> class Order2 {
> val a = "свойство".also { println("1: $it") }
> init { println("2: init") }
>
> constructor() { println("3: тело вторичного") }
> }
>
> fun main() { Order2() }
> ```
> Вывод: `1`, `2`, `3`. Подробнее: [Инициализация](Инициализация.md)
>
> **Два конструктора с одинаковой сигнатурой невозможны**, даже если смысл разный:
> ```kotlin
> class Range3 private constructor(val from: Int, val to: Int) {
> companion object {
> fun ofBounds(from: Int, to: Int) = Range3(from, to)
> fun ofStart(start: Int, length: Int) = Range3(start, start + length)
> }
> }
>
> fun main() { println(Range3.ofStart(1, 5).to) } // 6
> ```
> Фабричные функции решают это именами.
>
> **Исключение в конструкторе оставляет объект недосозданным.** Если в `init` что-то упало, ссылка на объект не вернётся - но уже выполненные побочные эффекты (запись в реестр, открытый файл) останутся. Поэтому в конструкторе делают только валидацию и присваивание.
Дополнительно: [Свойства](Свойства.md)
Далее: [Свойства](Свойства.md)