Spec-Zone.ru › Kotlin 2

Открытые и специальные классы

В этой главе вы узнаете об открытых классах, их взаимодействии с интерфейсами и других специальных типах классов, доступных в Kotlin.

Открытые классы

Если вы не можете использовать интерфейсы или абстрактные классы, можно явно разрешить наследование от класса, объявив его открытым. Для этого используйте ключевое слово open перед объявлением класса:

open class Vehicle(val make: String, val model: String)

Чтобы создать класс, наследующий другой класс, поставьте двоеточие после заголовка класса, а затем вызовите конструктор родительского класса, от которого хотите наследоваться. В этом примере класс Car наследуется от класса Vehicle:

open class Vehicle(val make: String, val model: String)

class Car(make: String, model: String, val numberOfDoors: Int) : Vehicle(make, model)

fun main() {
    // Creates an instance of the Car class
    val car = Car("Toyota", "Corolla", 4)

    // Prints the details of the car
    println("Car Info: Make - ${car.make}, Model - ${car.model}, Number of doors - ${car.numberOfDoors}")
    // Car Info: Make - Toyota, Model - Corolla, Number of doors - 4
}

Как и при создании обычного экземпляра класса, если ваш класс наследуется от родительского, он должен инициализировать все параметры, объявленные в заголовке родительского класса. Поэтому в примере экземпляр класса Car — car — инициализирует параметры родительского класса: make и model.

Переопределение унаследованного поведения

Если вы хотите наследоваться от класса, но изменить некоторые аспекты его поведения, можно переопределить унаследованное поведение.

По умолчанию переопределить функцию-член или свойство родительского класса нельзя. Как и в случае с абстрактными классами, для этого нужно добавить специальные ключевые слова.

Функции-члены

Чтобы разрешить переопределение функции в родительском классе, используйте ключевое слово open перед её объявлением в родительском классе:

open fun displayInfo() {}

Чтобы переопределить унаследованную функцию-член, используйте ключевое слово override перед объявлением функции в дочернем классе:

override fun displayInfo() {}

Например:

open class Vehicle(val make: String, val model: String) {
    open fun displayInfo() {
        println("Vehicle Info: Make - $make, Model - $model")
    }
}

class Car(make: String, model: String, val numberOfDoors: Int) : Vehicle(make, model) {
    override fun displayInfo() {
        println("Car Info: Make - $make, Model - $model, Number of Doors - $numberOfDoors")
    }
}

fun main() {
    val car1 = Car("Toyota", "Corolla", 4)
    val car2 = Car("Honda", "Civic", 2)

    // Uses the overridden displayInfo() function
    car1.displayInfo()
    // Car Info: Make - Toyota, Model - Corolla, Number of Doors - 4
    car2.displayInfo()
    // Car Info: Make - Honda, Model - Civic, Number of Doors - 2
}

В этом примере:

  • Создаются два экземпляра класса Car, наследующего класс Vehicle: car1 и car2.

  • В классе Car переопределяется функция displayInfo(), чтобы также выводить количество дверей.

  • Для экземпляров car1 и car2 вызывается переопределённая функция displayInfo().

Свойства

В Kotlin обычно не делают свойство наследуемым с помощью ключевого слова open, чтобы затем переопределить его. В большинстве случаев для этого используют абстрактный класс или интерфейс, в которых свойства наследуются по умолчанию.

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

Например, допустим, у вас есть свойство transmissionType, которое вы хотите переопределить позднее. Синтаксис переопределения свойств точно такой же, как и для функций-членов. Это можно сделать так:

open class Vehicle(val make: String, val model: String) {
    open val transmissionType: String = "Manual"
}

class Car(make: String, model: String, val numberOfDoors: Int) : Vehicle(make, model) {
    override val transmissionType: String = "Automatic"
}

Однако это считается плохой практикой. Вместо этого можно добавить свойство в конструктор класса, от которого разрешено наследоваться, и объявить его значение при создании дочернего класса Car:

open class Vehicle(val make: String, val model: String, val transmissionType: String = "Manual")

class Car(make: String, model: String, val numberOfDoors: Int) : Vehicle(make, model, "Automatic")

Прямой доступ к свойствам вместо их переопределения делает код проще и понятнее. Объявляя свойства один раз в родительском классе и передавая их значения через конструктор, вы избавляетесь от необходимости переопределять их в дочерних классах.

Подробнее о наследовании классов и переопределении поведения классов см. в разделе Наследование.

Открытые классы и интерфейсы

Можно создать класс, который наследуется от другого класса и реализует несколько интерфейсов. В этом случае после двоеточия сначала нужно объявить родительский класс, а затем перечислить интерфейсы:

// Define interfaces
interface EcoFriendly {
    val emissionLevel: String
}

interface ElectricVehicle {
    val batteryCapacity: Double
}

// Parent class
open class Vehicle(val make: String, val model: String)

// Child class
open class Car(make: String, model: String, val numberOfDoors: Int) : Vehicle(make, model)

// New class that inherits from Car and implements two interfaces
class ElectricCar(
    make: String,
    model: String,
    numberOfDoors: Int,
    val capacity: Double,
    val emission: String
) : Car(make, model, numberOfDoors), EcoFriendly, ElectricVehicle {
    override val batteryCapacity: Double = capacity
    override val emissionLevel: String = emission
}

Специальные классы

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

Запечатанные классы

Иногда требуется ограничить наследование. Для этого можно использовать запечатанные классы. Запечатанные классы — это особый тип абстрактного класса. После объявления класса запечатанным создавать его дочерние классы можно только в пределах того же пакета. Наследоваться от запечатанного класса вне этой области нельзя.

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

Чтобы создать запечатанный класс, используйте ключевое слово sealed:

sealed class Mammal

Запечатанные классы особенно полезны в сочетании с выражением when. С помощью выражения when можно определить поведение для всех возможных дочерних классов. Например:

sealed class Mammal(val name: String)

class Cat(val catName: String) : Mammal(catName)
class Human(val humanName: String, val job: String) : Mammal(humanName)

fun greetMammal(mammal: Mammal): String {
    when (mammal) {
        is Human -> return "Hello ${mammal.name}; You're working as a ${mammal.job}"
        is Cat -> return "Hello ${mammal.name}"   
    }
}

fun main() {
    println(greetMammal(Cat("Snowy")))
    // Hello Snowy
}

В этом примере:

  • Запечатанный класс Mammal имеет параметр name в конструкторе.

  • Класс Cat наследуется от запечатанного класса Mammal и использует параметр catName в своём конструкторе в качестве параметра name класса Mammal.

  • Класс Human наследуется от запечатанного класса Mammal и использует параметр humanName в своём конструкторе в качестве параметра name класса Mammal. В его конструкторе также есть параметр job.

  • Функция greetMammal() принимает аргумент типа Mammal и возвращает строку.

  • В теле функции greetMammal() находится выражение when, которое использует оператор is, чтобы проверить тип mammal и определить, какое действие выполнить.

  • Функция main() вызывает функцию greetMammal() с экземпляром класса Cat и параметром name под названием Snowy.

В этой статье оператор is подробнее рассматривается в главе Безопасность при работе с null.

Подробнее о запечатанных классах и рекомендуемых сценариях их использования см. в разделе Запечатанные классы и интерфейсы.

Классы перечислений

Классы перечислений полезны, когда нужно представить в классе конечный набор различных значений. Класс перечисления содержит константы перечисления, каждая из которых сама является экземпляром этого класса.

Чтобы создать класс перечисления, используйте ключевое слово enum:

enum class State

Допустим, вы хотите создать класс перечисления, содержащий различные состояния процесса. Каждая константа перечисления должна отделяться запятой ,:

enum class State {
    IDLE, RUNNING, FINISHED
}

Класс перечисления State содержит константы перечисления: IDLE, RUNNING и FINISHED. Чтобы обратиться к константе перечисления, укажите имя класса, затем . и имя константы перечисления:

val state = State.RUNNING

Этот класс перечисления можно использовать с выражением when, чтобы определить действие в зависимости от значения константы перечисления:

enum class State {
    IDLE, RUNNING, FINISHED
}

fun main() {
    val state = State.RUNNING
    val message = when (state) {
        State.IDLE -> "It's idle"
        State.RUNNING -> "It's running"
        State.FINISHED -> "It's finished"
    }
    println(message)
    // It's running
}

Классы перечислений, как и обычные классы, могут иметь свойства и функции-члены.

Например, допустим, вы работаете с HTML и хотите создать класс перечисления с несколькими цветами. Вы хотите, чтобы у каждого цвета было свойство rgb со значением RGB в шестнадцатеричном формате. При создании констант перечисления необходимо инициализировать это свойство:

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF),
    YELLOW(0xFFFF00)
}

Kotlin хранит шестнадцатеричные числа как целые, поэтому свойство rgb имеет тип Int, а не тип String.

Чтобы добавить в этот класс функцию-член, отделите её от констант перечисления точкой с запятой ;:

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF),
    YELLOW(0xFFFF00);

    fun containsRed() = (this.rgb and 0xFF0000 != 0)
}

fun main() {
    val red = Color.RED
    
    // Calls containsRed() function on enum constant
    println(red.containsRed())
    // true

    // Calls containsRed() function on enum constants via class names
    println(Color.BLUE.containsRed())
    // false
  
    println(Color.YELLOW.containsRed())
    // true
}

В этом примере функция-член containsRed() с помощью ключевого слова this обращается к значению свойства rgb константы перечисления и проверяет, содержит ли шестнадцатеричное значение FF в качестве первых битов, чтобы вернуть логическое значение.

Подробнее см. в разделе Классы перечислений.

Встраиваемые классы-значения

Иногда в коде требуется создавать небольшие объекты классов и использовать их лишь непродолжительное время. Такой подход может повлиять на производительность. Встраиваемые классы-значения — это особый тип классов, позволяющий избежать такого влияния на производительность. Однако они могут содержать только значения.

Чтобы создать встраиваемый класс-значение, используйте ключевое слово value и аннотацию @JvmInline:

@JvmInline
value class Email

Аннотация @JvmInline указывает Kotlin оптимизировать код при компиляции. Подробнее см. в разделе Аннотации.

У встраиваемого класса-значения должно быть одно свойство, инициализированное в заголовке класса.

Допустим, вы хотите создать класс для хранения адреса электронной почты:

// The address property is initialized in the class header.
@JvmInline
value class Email(val address: String)

fun sendEmail(email: Email) {
    println("Sending email to ${email.address}")
}

fun main() {
    val myEmail = Email("example@example.com")
    sendEmail(myEmail)
    // Sending email to example@example.com
}

В этом примере:

  • Email — это встраиваемый класс-значение с одним свойством в заголовке класса: address.

  • Функция sendEmail() принимает объекты типа Email и выводит строку в стандартный поток вывода.

  • Функция main():

    • Создаёт экземпляр класса Email с именем myEmail.

    • Вызывает функцию sendEmail() для объекта myEmail.

Используя встраиваемый класс-значение, вы встраиваете класс и можете использовать его непосредственно в коде, не создавая объект. Это позволяет значительно уменьшить объём занимаемой памяти и повысить производительность кода во время выполнения.

Подробнее о встраиваемых классах-значениях см. в разделе Встраиваемые классы-значения.

Практика

Упражнение 1

Вы управляете службой доставки и хотите отслеживать статусы посылок. Создайте запечатанный класс DeliveryStatus с классами данных, представляющими следующие статусы: Pending, InTransit, Delivered, Canceled. Дополните объявление класса DeliveryStatus так, чтобы код функции main() выполнялся без ошибок:

sealed class // Write your code here

fun printDeliveryStatus(status: DeliveryStatus) {
    when (status) {
        is DeliveryStatus.Pending -> {
            println("The package is pending pickup from ${status.sender}.")
        }
        is DeliveryStatus.InTransit -> {
            println("The package is in transit and expected to arrive by ${status.estimatedDeliveryDate}.")
        }
        is DeliveryStatus.Delivered -> {
            println("The package was delivered to ${status.recipient} on ${status.deliveryDate}.")
        }
        is DeliveryStatus.Canceled -> {
            println("The delivery was canceled due to: ${status.reason}.")
        }
    }
}

fun main() {
    val status1: DeliveryStatus = DeliveryStatus.Pending("Alice")
    val status2: DeliveryStatus = DeliveryStatus.InTransit("2024-11-20")
    val status3: DeliveryStatus = DeliveryStatus.Delivered("2024-11-18", "Bob")
    val status4: DeliveryStatus = DeliveryStatus.Canceled("Address not found")

    printDeliveryStatus(status1)
    // The package is pending pickup from Alice.
    printDeliveryStatus(status2)
    // The package is in transit and expected to arrive by 2024-11-20.
    printDeliveryStatus(status3)
    // The package was delivered to Bob on 2024-11-18.
    printDeliveryStatus(status4)
    // The delivery was canceled due to: Address not found.
}
sealed class DeliveryStatus { data class Pending(val sender: String) : DeliveryStatus() data class InTransit(val estimatedDeliveryDate: String) : DeliveryStatus() data class Delivered(val deliveryDate: String, val recipient: String) : DeliveryStatus() data class Canceled(val reason: String) : DeliveryStatus() } fun printDeliveryStatus(status: DeliveryStatus) { when (status) { is DeliveryStatus.Pending -> { println("The package is pending pickup from ${status.sender}.") } is DeliveryStatus.InTransit -> { println("The package is in transit and expected to arrive by ${status.estimatedDeliveryDate}.") } is DeliveryStatus.Delivered -> { println("The package was delivered to ${status.recipient} on ${status.deliveryDate}.") } is DeliveryStatus.Canceled -> { println("The delivery was canceled due to: ${status.reason}.") } } } fun main() { val status1: DeliveryStatus = DeliveryStatus.Pending("Alice") val status2: DeliveryStatus = DeliveryStatus.InTransit("2024-11-20") val status3: DeliveryStatus = DeliveryStatus.Delivered("2024-11-18", "Bob") val status4: DeliveryStatus = DeliveryStatus.Canceled("Address not found") printDeliveryStatus(status1) // The package is pending pickup from Alice. printDeliveryStatus(status2) // The package is in transit and expected to arrive by 2024-11-20. printDeliveryStatus(status3) // The package was delivered to Bob on 2024-11-18. printDeliveryStatus(status4) // The delivery was canceled due to: Address not found. }

Упражнение 2

В программе нужно обрабатывать различные статусы и типы ошибок. У вас есть запечатанный класс для представления разных статусов, объявленных в классах данных или объектах. Дополните приведённый ниже код, создав класс перечисления Problem, представляющий разные типы проблем: NETWORK, TIMEOUT и UNKNOWN.

sealed class Status {
    data object Loading : Status()
    data class Error(val problem: Problem) : Status() {
        // Write your code here
    }

    data class OK(val data: List<String>) : Status()
}

fun handleStatus(status: Status) {
    when (status) {
        is Status.Loading -> println("Loading...")
        is Status.OK -> println("Data received: ${status.data}")
        is Status.Error -> when (status.problem) {
            Status.Error.Problem.NETWORK -> println("Network issue")
            Status.Error.Problem.TIMEOUT -> println("Request timed out")
            Status.Error.Problem.UNKNOWN -> println("Unknown error occurred")
        }
    }
}

fun main() {
    val status1: Status = Status.Error(Status.Error.Problem.NETWORK)
    val status2: Status = Status.OK(listOf("Data1", "Data2"))

    handleStatus(status1)
    // Network issue
    handleStatus(status2)
    // Data received: [Data1, Data2]
}
sealed class Status { data object Loading : Status() data class Error(val problem: Problem) : Status() { enum class Problem { NETWORK, TIMEOUT, UNKNOWN } } data class OK(val data: List<String>) : Status() } fun handleStatus(status: Status) { when (status) { is Status.Loading -> println("Loading...") is Status.OK -> println("Data received: ${status.data}") is Status.Error -> when (status.problem) { Status.Error.Problem.NETWORK -> println("Network issue") Status.Error.Problem.TIMEOUT -> println("Request timed out") Status.Error.Problem.UNKNOWN -> println("Unknown error occurred") } } } fun main() { val status1: Status = Status.Error(Status.Error.Problem.NETWORK) val status2: Status = Status.OK(listOf("Data1", "Data2")) handleStatus(status1) // Network issue handleStatus(status2) // Data received: [Data1, Data2] }
  • Предыдущий шаг

  • Следующий шаг

01 июля 2026
ОбъектыСвойства

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/kotlin-tour-intermediate-open-special-classes.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API