Объектно-ориентированное программирование в Kotlin
Play ITЗагрузка интерактивного демо…
Если ООП для вас новое или вы учите Kotlin с нуля, сначала пройдите материалы без привязки к синтаксису: парадигмы и уровни абстракции, затем ООП — о разделе — зачем объекты, введение, абстракция, инкапсуляция, наследование, полиморфизм.
Ниже — как это устроено в Kotlin.
Терминология — общее ООП и Kotlin
| Понятие ООП | Общая идея | Как выражено в Kotlin |
|---|---|---|
| Класс | чертёж объекта с полями и методами | class; по умолчанию final (закрыт для наследования) |
| Объект (экземпляр) | конкретная сущность в памяти | val u = User("Ann") — без ключевого слова new |
| Инкапсуляция | сокрытие состояния, доступ через API | private/protected/internal; свойства с кастомными геттерами/сеттерами |
| Наследование | расширение поведения базового типа | один суперкласс (open class), синтаксис : Base() |
| Реализация контракта | обязанность реализовать методы | interface, abstract class; несколько интерфейсов у одного класса |
| Полиморфизм подтипов | вызов по ссылке на супертип | override, виртуальные вызовы JVM; when с sealed |
| Абстракция | скрытие деталей, контракт | abstract class, interface с реализациями по умолчанию |
| АДТ (алгебраический тип данных) | тип, описывающий структуру данных | data class — автогенерация equals/hashCode/copy |
| Закрытая иерархия | фиксированный набор подтипов | sealed class / sealed interface |
| Синглтон | один экземпляр на приложение | object, companion object |
| Композиция | поведение через делегирование | class A : B by delegate, делегированные свойства by lazy |
| Статические члены | общие для типа, не для экземпляра | companion object, @JvmStatic для Java |
| Фабрика | создание без прямого constructor | companion object { fun create(...) } |
| Параметрический полиморфизм | обобщённые типы (generics) | List<T>; reified при inline |
| Расширение типа | новое поведение без наследования | extension functions (не полиморфны) |
Определения без привязки к языку — раздел 4-08-oop. Обзор языка — Kotlin.
Кратко для новичка:
- Класс — заголовок файла; первичный конструктор в объявлении
class User(val name: String). val— только чтение после инициализации;var— можно менять.- Классы по умолчанию
final— для наследования нужен модификаторopen. data class— автоматическиеequals,hashCode,copyдля записей данных.- Интерфейс — методы с реализацией по умолчанию;
object— singleton.
ООП в Kotlin — обзор
Kotlin — статически типизированный язык для JVM (Java Virtual Machine — среда выполнения байткода Java). Объектная модель здесь — основа архитектуры: даже числа на JVM представлены классами-обёртками, а бизнес-логика обычно группируется в классы, интерфейсы и закрытые иерархии (sealed).
Kotlin полностью совместим с Java и сокращает шаблонный код (boilerplate — повторяющиеся объявления, которые компилятор пишет сам): свойства вместо полей с геттерами, первичные конструкторы, data class, встроенное делегирование by.
Разбор:
- Статическая типизация — тип переменной проверяется до запуска программы; ошибки ловятся компилятором.
- JVM — Kotlin-код компилируется в тот же байткод, что и Java; классы из
.ktи.javaживут в одном проекте. data class— компилятор сам генерирует сравнение, копирование и строковое представление для моделей данных.sealed— компилятор знает полный список подтипов и проверяет исчерпывающийwhen.
Интерактивная схема — класс и объект (псевдокод, подходит для любого ООП-языка). Полный разбор принципов: ООП в разделе "Код и разработка".
КЛАСС Кот
поля: имя, возраст
метод мяукнуть()
КОНЕЦ
объект barsik := новый Кот(имя="Барсик", возраст=3)
barsik.мяукнуть()
Play ITЗагрузка интерактивного демо…
Пример класса
Код ITЗагрузка примера кода…
Разбор:
class Unit— шаблон игрового персонажа: поля (health,name) и методы (attack) в одном типе.var health— свойство можно переназначить;val name— только при создании объекта.damage— вычисляемое свойство: при каждом обращении срабатывает геттер, отдельного поля нет.attack(target)— объект меняет состояние другого объекта через метод, а не прямую запись в чужие поля (инкапсуляция).fun main()на верхнем уровне файла — идиоматичная точка входа в Kotlin (аналогpublic static void mainв Java).
Создание экземпляров
Первичный конструктор
Параметры в заголовке класса — это первичный конструктор. Если параметр помечен val или var, компилятор автоматически создаёт свойство:
class Person(val name: String, var age: Int) {
fun greet() = println("Hello, $name")
}
val person = Person("Alice", 30)
person.age = 31
Первичный конструктор не может содержать исполняемый код — только объявление параметров. Вся инициализация выполняется в блоках init или через значения по умолчанию параметров.
Блоки init
Блоки init выполняются после инициализации свойств первичного конструктора, в порядке объявления в теле класса:
class Order(val id: String, items: List<String>) {
val itemCount: Int
init {
require(items.isNotEmpty()) { "Заказ не может быть пустым" }
itemCount = items.size
println("Создан заказ $id с $itemCount позициями")
}
init {
// второй блок — для логического разделения шагов
check(id.isNotBlank())
}
}
init-блоки удобны для валидации инвариантов, логирования и подготовки производных полей. Они имеют доступ ко всем свойствам, уже созданным к моменту выполнения.
Вторичный конструктор
Если нужны альтернативные способы создания объекта, объявляют вторичные конструкторы с ключевым словом constructor:
class Rectangle(val width: Int, val height: Int) {
constructor(side: Int) : this(side, side) {
println("Создан квадрат $side×$side")
}
constructor() : this(1, 1) // единичный прямоугольник по умолчанию
}
val square = Rectangle(10) // квадрат 10×10
val unit = Rectangle() // 1×1
val rect = Rectangle(3, 7) // через первичный конструктор
Каждый вторичный конструктор обязан делегировать первичному (: this(...)) или другому вторичному. В классе без явного первичного конструктора компилятор создаёт неявный пустой constructor().
Порядок инициализации при наследовании
При создании наследника порядок такой:
- Инициализация свойств и
init-блоков базового класса (после вызова его конструктора из наследника). - Инициализация свойств и
init-блоков производного класса. - Тело вторичного конструктора (если используется).
open class Base(val label: String) {
init { println("Base init: $label") }
}
class Derived(label: String, val extra: Int) : Base(label) {
init { println("Derived init: $extra") }
}
// Derived("x", 1) → Base init: x → Derived init: 1
Фабрика через companion object
Для сложной логики создания (парсинг, кэш, выбор реализации) вместо публичного конструктора используют фабричные методы в компаньоне:
class Email private constructor(val address: String) {
companion object {
private val cache = mutableMapOf<String, Email>()
fun parse(raw: String): Email {
val normalized = raw.trim().lowercase()
require("@" in normalized) { "Некорректный email: $raw" }
return cache.getOrPut(normalized) { Email(normalized) }
}
fun empty() = Email("")
}
}
val e1 = Email.parse("User@Example.com")
val e2 = Email.parse("user@example.com") // тот же экземпляр из кэша
private constructor запрещает создание снаружи класса; единственный публичный путь — Email.parse(...). Такой паттерн часто встречается в Android (ViewModel через Factory) и при работе с value objects.
Свойства и переменные
val и var
| Ключевое слово | Ссылка на объект | Содержимое объекта |
|---|---|---|
val | не переназначается | изменяемо, если объект мутабельный (MutableList, var внутри data class) |
var | переназначается | то же |
val names = mutableListOf("Ann")
names.add("Bob") // OK — меняем содержимое
// names = mutableListOf() // ошибка — ссылку менять нельзя
var counter = 0
counter = 1 // OK
Для доменных моделей предпочтительны иммутабельные data class с val: копирование через copy() безопаснее для многопоточности и предсказуемее при отладке.
Вычисляемые свойства и кастомные аксессоры
class Temperature(celsius: Double) {
var celsius = celsius
set(value) {
field = value.coerceIn(-273.15, 1_000_000.0)
}
val fahrenheit: Double
get() = celsius * 9 / 5 + 32
}
field — ссылка на скрытое поле (backing field), которое компилятор создаёт для свойства; без field сеттер или геттер рекурсивно вызовут сами себя. Модификатор private set оставляет публичное чтение, но запрещает присваивание снаружи:
class Counter {
var count: Int = 0
private set
fun increment() { count++ }
}
lateinit — отложенная инициализация ссылок
Для изменяемых (var) ссылочных типов, которые нельзя задать в конструкторе (например, Android View после onCreate), используют lateinit:
class MainActivity {
lateinit var adapter: ItemAdapter
fun onCreate() {
adapter = ItemAdapter()
}
fun render() {
check(::adapter.isInitialized) { "adapter не инициализирован" }
adapter.notifyDataSetChanged()
}
}
Ограничения: только var, только non-null ссылочные типы, нельзя для примитивов (Int, Boolean). Обращение до инициализации — UninitializedPropertyAccessException.
lazy — ленивая инициализация val
class ReportService(private val repo: ReportRepository) {
val heavyConfig: Config by lazy {
println("Загрузка конфигурации...")
repo.loadConfig()
}
}
lazy вычисляет значение один раз при первом обращении. По умолчанию потокобезопасен (LazyThreadSafetyMode.SYNCHRONIZED). Подходит для дорогих ресурсов, которые могут и не понадобиться.
Делегированные свойства
Ключевое слово by переносит логику хранения и доступа в отдельный объект-делегат:
import kotlin.properties.Delegates
class UserProfile {
var displayName: String by Delegates.observable("Guest") { _, old, new ->
println("Имя: $old → $new")
}
val permissions: Set<String> by lazy {
loadPermissions()
}
}
Стандартная библиотека предоставляет lazy, Delegates.observable, Delegates.vetoable. В Android и DI-фреймворках популярны кастомные делегаты (by viewModels(), by inject()). Семантика для вызывающего кода остаётся как у обычного свойства.
Модификаторы доступа и наследования
Видимость — public, private, protected, internal
| Модификатор | Класс | Подкласс (другой файл) | Тот же модуль | Любой код |
|---|---|---|---|---|
public (по умолчанию) | да | да | да | да |
protected | да | да | — | нет |
internal | да | да* | да | нет** |
private | да | нет | нет | нет |
* protected/internal члены базового класса видны наследнику.
** internal скрыт за пределами Gradle/Maven-модуля.
// Файл A.kt, модуль app
internal class Repository {
private fun query() = Unit
protected fun cache() = Unit
}
// Файл B.kt, тот же модуль
class CachedRepo : Repository() {
fun warmUp() { cache() } // protected — OK
// query() — ошибка: private
}
Для top-level объявлений private ограничивает видимость файлом, а не классом.
open, final и закрытые по умолчанию классы
В отличие от Java, классы и методы в Kotlin финальны по умолчанию:
class Locked // нельзя наследовать
open class Extensible // можно наследовать
open class Base {
open fun tweak() {}
fun stable() {} // final — переопределить нельзя
}
Явное final на члене запрещает переопределение даже в open-классе. Это защищает инварианты: автор класса явно помечает точки расширения.
sealed — закрытые иерархии
sealed class / sealed interface фиксируют множество прямых наследников (в том же модуле и, для классов, обычно в том же файле):
sealed class UiState {
data object Idle : UiState()
data class Loading(val progress: Int) : UiState()
data class Content(val data: List<String>) : UiState()
data class Error(val cause: Throwable) : UiState()
}
Подклассы sealed-типа не обязаны быть в том же файле, но должны быть в том же модуле и вложены или перечислены в теле sealed-класса (Kotlin 1.5+). Компилятор знает полный набор вариантов и проверяет исчерпывающий when.
Четыре столпа ООП в Kotlin
Интерактивные схемы — общие принципы (псевдокод). Подробнее: инкапсуляция, наследование, полиморфизм, абстракция.
Play ITЗагрузка интерактивного демо…
Play ITЗагрузка интерактивного демо…
Play ITЗагрузка интерактивного демо…
Play ITЗагрузка интерактивного демо…
В Kotlin все четыре идеи работают поверх JVM: виртуальные вызовы, иерархии классов и интерфейсов. Главные отличия от Java — классы закрыты для наследования по умолчанию, свойства вместо полей, sealed для алгебраических типов, делегирование by для композиции.
Инкапсуляция
Инкапсуляция — сокрытие внутреннего состояния и предоставление контролируемого API. В Kotlin она выражается через модификаторы видимости, свойства с валидацией в сеттерах и отсутствие публичных мутабельных полей в хорошо спроектированных классах.
class BankAccount(initial: Int) {
private var balance = initial
fun deposit(amount: Int) {
require(amount > 0)
balance += amount
}
fun withdraw(amount: Int): Boolean {
if (amount <= 0 || amount > balance) return false
balance -= amount
return true
}
fun currentBalance() = balance
}
Клиент не может напрямую изменить balance — только через методы с проверками. Свойства компилируются в private-поля и публичные геттеры/сеттеры, совместимые с JavaBeans, поэтому инкапсуляция Kotlin-классов прозрачна для Java-фреймворков (JPA, Jackson).
Паттерн backing field с подчёркиванием (_passwordHash) отделяет внутреннее представление от публичного API:
class User(private val login: String) {
private var _passwordHash: String? = null
fun setPassword(password: String) {
_passwordHash = hash(password)
}
private fun hash(s: String): String = /* ... */
}
Наследование
Kotlin поддерживает один суперкласс и несколько интерфейсов. Синтаксис наследования — двоеточие после имени класса:
open class Animal(val name: String) {
open fun speak() = println("$name издаёт звук")
}
class Dog(name: String, val breed: String) : Animal(name) {
override fun speak() = println("$name ($breed): гав!")
}
interface Pet {
fun play()
}
class Cat(name: String) : Animal(name), Pet {
override fun speak() = println("$name: мяу")
override fun play() = println("$name играет")
}
Вызов конструктора родителя (Animal(name)) встроен в объявление наследования — нельзя забыть super(), как иногда бывает в Java. Если у базового класса нет подходящего конструктора, наследование невозможно без явного вторичного конструктора с делегированием.
super и множественное наследование поведения
При конфликте реализаций из нескольких интерфейсов указывают явно:
interface A { fun foo() = println("A") }
interface B { fun foo() = println("B") }
class C : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
}
Полиморфизм
Полиморфизм подтипов: переменная супертипа ссылается на объект подтипа, а вызывается переопределённая реализация:
open class Shape {
open fun draw() = println("Фигура")
}
class Circle : Shape() {
override fun draw() = println("Круг")
}
fun render(shape: Shape) = shape.draw()
render(Circle()) // "Круг" — динамическое связывание
Метод базового класса должен быть open, в наследнике — override. Ключевое слово override обязательно: компилятор проверяет соответствие сигнатуры. Интерфейсы и их методы open по умолчанию (с Kotlin 1.4+ можно пометить fun interface для SAM).
Полиморфизм через sealed и when
Для закрытых иерархий полиморфизм часто выражают исчерпывающим when вместо цепочки if/instanceof:
fun describe(state: UiState): String = when (state) {
UiState.Idle -> "ожидание"
is UiState.Loading -> "загрузка ${state.progress}%"
is UiState.Content -> "данных: ${state.data.size}"
is UiState.Error -> "ошибка: ${state.cause.message}"
}
Если перечислены все ветки sealed-типа, else не нужен — компилятор гарантирует полноту. Добавление нового подтипа без новой ветки when — ошибка компиляции, а не runtime-сюрприз.
Дополнительные сниппеты
open class Notification {
open fun send() = println("Отправка базового уведомления")
}
class EmailNotification : Notification() {
override fun send() = println("Отправка email-уведомления")
}
fun dispatch(notification: Notification) {
notification.send()
}
dispatch принимает базовый тип и вызывает реализацию конкретного объекта — классический полиморфизм. Новый канал (SmsNotification) добавляется без изменения dispatch.
Абстракция
Абстракция отделяет контракт (что нужно сделать) от реализации (как именно). В Kotlin — через abstract class и interface.
abstract class Repository<T> {
abstract fun findById(id: String): T?
fun mustFindById(id: String): T =
findById(id) ?: error("Не найдено: $id")
}
class InMemoryUserRepo : Repository<User>() {
private val store = mutableMapOf<String, User>()
override fun findById(id: String) = store[id]
}
mustFindById — шаблонный метод с общей логикой; findById — точка расширения для подклассов.
Интерфейсы и абстрактные классы
Интерфейс и абстрактный класс
Сравнение с примерами и таблицей — Абстракция в ООП. В Kotlin:
interface | abstract class | |
|---|---|---|
| Роль | Контракт поведения (Drawable, WiFiConnectable) | Общий предок с полями и логикой |
| Поля | abstract / open свойства или геттеры без хранения в интерфейсе | Обычные свойства и поля в теле класса |
| Конструктор | Нет | Есть — инициализация общего состояния |
| Сколько можно указать | Несколько интерфейсов | Один суперкласс |
| Готовый код в методах | Да, тело прямо в интерфейсе | Да, в неабстрактных методах |
| Типичный случай | Разные классы с одной способностью | Родственные типы с общим кодом |
Практическое правило:
- нужен только контракт без общего состояния →
interface; - есть общие поля и шаблонная логика →
abstract class; - набор взаимоисключающих вариантов с данными →
sealed classвместо глубокой иерархии.
interface Drawable {
fun draw()
fun render() {
println("Подготовка холста")
draw()
}
}
abstract class AbstractShape(val color: String) : Drawable {
abstract override fun draw()
}
class Rectangle(color: String, val w: Int, val h: Int) : AbstractShape(color) {
override fun draw() = println("Прямоугольник $w×$h, цвет $color")
}
render() в интерфейсе — реализация по умолчанию (аналог default в Java 8+). Подкласс обязан дописать только draw().
Функциональные интерфейсы (SAM)
SAM (Single Abstract Method) — интерфейс с ровно одним абстрактным методом; в Kotlin помечается fun interface:
fun interface Predicate<T> {
fun test(value: T): Boolean
}
val isEven = Predicate<Int> { it % 2 == 0 }
Разбор:
- Один абстрактный метод — можно передать лямбду вместо анонимного объекта.
- Удобно для колбэков и Java SAM-типов (
Runnable,Comparator). - Компилятор генерирует класс-обёртку, совместимый с Java-вызовами.
data class, object и sealed class
data class
Компилятор генерирует equals, hashCode, toString, copy, componentN() для деструктуризации:
data class User(val id: Int, val name: String, val email: String)
val u = User(1, "Ann", "a@b.c")
val updated = u.copy(name = "Anna")
val (id, name, _) = u
В equals/hashCode участвуют только свойства первичного конструктора. Поля, объявленные в теле класса, игнорируются — важно не смешивать идентичность и вспомогательное состояние.
Ограничения: data class не может быть abstract, open, sealed или inner. Для иерархий с вариантами используют sealed class с data class-наследниками.
object — синглтон и выражения
Именованный синглтон — один экземпляр на классloader:
object DatabaseConnectionFactory : ConnectionFactory {
override fun create(): Connection =
DriverManager.getConnection(url, user, pass)
}
Объектное выражение — анонимный класс, объявленный в месте использования:
val comparator = object : Comparator<String> {
override fun compare(a: String, b: String) = a.length - b.length
}
В отличие от Java, объектное выражение может наследовать класс и реализовать несколько интерфейсов: object : Base(), A, B { }.
sealed class в моделировании состояний
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Failure(val error: String) : Result<Nothing>()
data object Loading : Result<Nothing>()
}
fun <T> handle(result: Result<T>) = when (result) {
is Result.Success -> println(result.data)
is Result.Failure -> println(result.error)
Result.Loading -> println("...")
}
Loading как data object (Kotlin 1.9+) — синглтон-вариант без полей. Result<Nothing> в ветках ошибки и загрузки корректно сужает generic.
Статические члены — companion object
Kotlin не имеет ключевого слова static. Вместо него — companion object, полноценный singleton внутри класса:
class Database {
companion object {
const val VERSION = "2.1"
fun connect(url: String): Connection { /* ... */ }
}
}
val v = Database.VERSION
val conn = Database.connect("jdbc:...")
const val встраивается в байткод клиента (как static final в Java). Для вызова из Java без префикса Companion используют @JvmStatic. Компаньон может реализовывать интерфейсы — удобно для тестовых фабрик и парсеров (companion object : Parser<User>).
Делегирование
Делегирование реализации интерфейса
Ключевое слово by в списке супертипов перенаправляет вызовы интерфейса другому объекту:
interface Printer {
fun print(message: String)
}
class ConsolePrinter : Printer {
override fun print(message: String) = println(message)
}
class LoggingPrinter(printer: Printer) : Printer by printer
Компилятор генерирует прокси-методы — аналог Decorator без boilerplate. Можно переопределить отдельные методы, остальные уйдут делегату.
Делегированные свойства
См. раздел Свойства и переменные: by lazy, Delegates.observable, кастомные ReadWriteProperty. Принцип композиции вместо наследования в Kotlin выражен конструкцией by, а не только рекомендацией из учебников.
Вложенные и внутренние классы
| Вид | Ключевое слово | Доступ к внешнему экземпляру | Аналог в Java |
|---|---|---|---|
| Вложенный | (по умолчанию) class Nested | нет | static class |
| Внутренний | inner class Inner | да | non-static inner class |
class Outer(private val label: String) {
inner class Inner {
fun describe() = "Внутри $label"
}
class Helper {
fun code() = "Статический по смыслу помощник"
}
}
val inner = Outer("app").Inner()
inner хранит ссылку на внешний объект (this@Outer). Для builder'ов и DTO чаще используют вложенные не-inner классы, чтобы не удерживать лишние ссылки.
Расширения (extensions)
Extension-функции добавляют методы к существующим типам без наследования и без изменения байткода получателя:
fun String.addExclamation() = "$this!"
println("Hello".addExclamation())
На JVM это статический метод ExtensionsKt.addExclamation(str). Расширения не полиморфны: выбор функции — на этапе компиляции по статическому типу. Не заменяют ООП-наследование, но уменьшают раздувание утилитных классов.
Сравнение Kotlin с Java
Полное сравнение с таблицами, примерами кода, корутинами и виртуальными потоками — Сравнение Java и Kotlin. Ниже — краткая шпаргалка по объектной модели.
| Тема | Kotlin | Java |
|---|---|---|
| Создание объекта | User("Ann") | new User("Ann") |
| Класс по умолчанию | final | открыт для наследования |
| Метод по умолчанию | final | виртуальный (переопределяемый) |
| Поля | свойства val/var | поля + геттеры/сеттеры вручную |
| Первичный конструктор | в заголовке class | только явные конструкторы |
static | companion object, @JvmStatic | static |
| Интерфейсы с телом | с Kotlin 1.0 | с Java 8 (default) |
| Множественное наследование классов | нет | нет |
| Null-безопасность | в системе типов (?) | Optional, аннотации |
| Data-классы | data class | record (Java 16+) |
| Закрытая иерархия | sealed class | sealed (Java 17+) |
| Паттерн Singleton | object | enum single value / holder |
| Перегрузка операторов | operator fun | нет (кроме + для String) |
| Делегирование | by встроено | вручную или библиотеки |
Kotlin-код компилируется в JVM-байткод, совместимый с Java 6+. data class и компаньоны генерируют методы, ожидаемые Java-библиотеками (equals, hashCode, copy, статические фабрики). Миграция модулей может идти постепенно: один JAR — смесь .kt и .java; практика interop — Kotlin и Java — совместимость.
Пример вызова Kotlin из Java:
// Java
User user = new User(1, "Alice");
String s = user.toString();
User copy = user.copy(2, "Bob");
Типичные ошибки
| Ошибка | Последствие | Что делать |
|---|---|---|
Наследование без open у базового класса/метода | ошибка компиляции или нежелательное расширение | явно помечать open только точки расширения |
Публичные var в доменной модели | обход инвариантов снаружи | private set, методы с валидацией, data class с val |
data class с полями только в теле класса | equals/hashCode не учитывают важное состояние | все значимые поля — в первичный конструктор |
lateinit для примитива или без проверки | UninitializedPropertyAccessException | lazy, nullable, или check(::x.isInitialized) |
Забытый override | ошибка компиляции (хорошо) или путаница при чтении Java-кода | всегда override; в Java — @Override |
Глубокие иерархии вместо sealed | неполный when, runtime else | sealed class для конечных наборов вариантов |
| Extension вместо полиморфизма | неверное поведение при ссылке на супертип | переопределение в иерархии или интерфейс |
!! на nullable-типах | NullPointerException в runtime | ?., ?:, проверки, ранний return |
inner class без необходимости | утечка ссылки на внешний объект | вложенный класс без inner |
| Интерфейс с неявным состоянием | неочевидные контракты | состояние в abstract class, интерфейс — поведение |
Чеклист понимания
- Объяснить разницу первичного и вторичного конструктора; написать класс с двумя
init-блоками и фабрикой вcompanion object. - Создать класс с
val,var, вычисляемым свойством иprivate set. - Показать
lateinitиlazyв разных сценариях; объяснить, когда каждый уместен. - Перечислить модификаторы видимости и сказать, где виден
internalчлен. - Построить иерархию
open class→ наследник сoverride; вызвать метод по ссылке на базовый тип. - Реализовать
interfaceс default-методом иabstract classс шаблонным методом — сравнить. - Описать
data class,object,sealed classна одном примере (например, сетевойResult). - Написать класс с делегированием интерфейса
byи свойствомby lazy. - Назвать пять отличий Kotlin от Java в объектной модели.
- Найти в своём коде (или учебном) один случай, где
sealedлучше, чем открытое наследование.
См. также
| Тема | Статья |
|---|---|
| Общие принципы ООП | 4-08-oop |
| Обзор Kotlin | раздел Kotlin |
| Null-безопасность | типы и null |
| Корутины | асинхронность в Kotlin |
| Паттерны проектирования | design-patterns |
| Сравнение Java и Kotlin | Java и Kotlin |
| Классы в Java | ООП Java |
Учебные примеры ООП
Небольшие самодостаточные программы, которые показывают классы, объекты, инкапсуляцию, наследование и взаимодействие нескольких типов на одной предметной области.
Класс и объект
Чертёж класса Figure и конкретные объекты — круг и квадрат.
Код ITЗагрузка примера кода…
Банковский счёт
Инкапсуляция: скрытое поле баланса и методы deposit/withdraw.
Код ITЗагрузка примера кода…
Наследование
Родитель Animal и дочерние Cat и Dog с общим eat() и своим speak().
Код ITЗагрузка примера кода…
Смартфон
Состояние объекта: заряд батареи, звонки и подзарядка.
Код ITЗагрузка примера кода…
Студент
Список оценок, средний балл и проходной порог.
Код ITЗагрузка примера кода…
Корзина покупок
Взаимодействие Product, Cart и Order при оформлении заказа.
Код ITЗагрузка примера кода…
Автомобиль
Пробег, расход топлива и напоминание о техобслуживании.
Код ITЗагрузка примера кода…
Пользователь
Скрытый пароль, вход в систему и публикация сообщений.
Код ITЗагрузка примера кода…