Перейти к основному содержимому

Объектно-ориентированное программирование в Kotlin

Play ITЗагрузка интерактивного демо…

Разработчику Архитектору
Сначала — общие понятия (раздел 4 "Код")

Если ООП для вас новое или вы учите Kotlin с нуля, сначала пройдите материалы без привязки к синтаксису: парадигмы и уровни абстракции, затем ООП — о разделезачем объекты, введение, абстракция, инкапсуляция, наследование, полиморфизм.

Ниже — как это устроено в Kotlin.

Терминология — общее ООП и Kotlin

Понятие ООПОбщая идеяКак выражено в Kotlin
Классчертёж объекта с полями и методамиclass; по умолчанию final (закрыт для наследования)
Объект (экземпляр)конкретная сущность в памятиval u = User("Ann") — без ключевого слова new
Инкапсуляциясокрытие состояния, доступ через APIprivate/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
Фабрикасоздание без прямого constructorcompanion 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().

Порядок инициализации при наследовании

При создании наследника порядок такой:

  1. Инициализация свойств и init-блоков базового класса (после вызова его конструктора из наследника).
  2. Инициализация свойств и init-блоков производного класса.
  3. Тело вторичного конструктора (если используется).
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:

interfaceabstract 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. Ниже — краткая шпаргалка по объектной модели.

ТемаKotlinJava
Создание объектаUser("Ann")new User("Ann")
Класс по умолчаниюfinalоткрыт для наследования
Метод по умолчаниюfinalвиртуальный (переопределяемый)
Полясвойства val/varполя + геттеры/сеттеры вручную
Первичный конструкторв заголовке classтолько явные конструкторы
staticcompanion object, @JvmStaticstatic
Интерфейсы с теломс Kotlin 1.0с Java 8 (default)
Множественное наследование классовнетнет
Null-безопасностьв системе типов (?)Optional, аннотации
Data-классыdata classrecord (Java 16+)
Закрытая иерархияsealed classsealed (Java 17+)
Паттерн Singletonobjectenum 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 для примитива или без проверкиUninitializedPropertyAccessExceptionlazy, nullable, или check(::x.isInitialized)
Забытый overrideошибка компиляции (хорошо) или путаница при чтении Java-кодавсегда override; в Java — @Override
Глубокие иерархии вместо sealedнеполный when, runtime elsesealed class для конечных наборов вариантов
Extension вместо полиморфизманеверное поведение при ссылке на супертиппереопределение в иерархии или интерфейс
!! на nullable-типахNullPointerException в runtime?., ?:, проверки, ранний return
inner class без необходимостиутечка ссылки на внешний объектвложенный класс без inner
Интерфейс с неявным состояниемнеочевидные контрактысостояние в abstract class, интерфейс — поведение

Чеклист понимания

  1. Объяснить разницу первичного и вторичного конструктора; написать класс с двумя init-блоками и фабрикой в companion object.
  2. Создать класс с val, var, вычисляемым свойством и private set.
  3. Показать lateinit и lazy в разных сценариях; объяснить, когда каждый уместен.
  4. Перечислить модификаторы видимости и сказать, где виден internal член.
  5. Построить иерархию open class → наследник с override; вызвать метод по ссылке на базовый тип.
  6. Реализовать interface с default-методом и abstract class с шаблонным методом — сравнить.
  7. Описать data class, object, sealed class на одном примере (например, сетевой Result).
  8. Написать класс с делегированием интерфейса by и свойством by lazy.
  9. Назвать пять отличий Kotlin от Java в объектной модели.
  10. Найти в своём коде (или учебном) один случай, где sealed лучше, чем открытое наследование.

См. также

ТемаСтатья
Общие принципы ООП4-08-oop
Обзор Kotlinраздел Kotlin
Null-безопасностьтипы и null
Корутиныасинхронность в Kotlin
Паттерны проектированияdesign-patterns
Сравнение Java и KotlinJava и Kotlin
Классы в JavaООП Java

Учебные примеры ООП

Небольшие самодостаточные программы, которые показывают классы, объекты, инкапсуляцию, наследование и взаимодействие нескольких типов на одной предметной области.

Класс и объект

Чертёж класса Figure и конкретные объекты — круг и квадрат.

Код ITЗагрузка примера кода…


Банковский счёт

Инкапсуляция: скрытое поле баланса и методы deposit/withdraw.

Код ITЗагрузка примера кода…


Наследование

Родитель Animal и дочерние Cat и Dog с общим eat() и своим speak().

Код ITЗагрузка примера кода…


Смартфон

Состояние объекта: заряд батареи, звонки и подзарядка.

Код ITЗагрузка примера кода…


Студент

Список оценок, средний балл и проходной порог.

Код ITЗагрузка примера кода…


Корзина покупок

Взаимодействие Product, Cart и Order при оформлении заказа.

Код ITЗагрузка примера кода…


Автомобиль

Пробег, расход топлива и напоминание о техобслуживании.

Код ITЗагрузка примера кода…


Пользователь

Скрытый пароль, вход в систему и публикация сообщений.

Код ITЗагрузка примера кода…

Содержание