Spec-Zone.ru › Kotlin 2

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

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

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

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

value class Password(private val s: String)

Чтобы объявить встраиваемый класс для бэкенда JVM, используйте модификатор value вместе с аннотацией @JvmInline перед объявлением класса:

// For JVM backends
@JvmInline
value class Password(private val s: String)

Встраиваемый класс должен иметь одно свойство, инициализируемое в первичном конструкторе. Во время выполнения экземпляры встраиваемого класса будут представлены с помощью этого единственного свойства (подробнее о представлении во время выполнения см. ниже):

// No actual instantiation of class 'Password' happens
// At runtime 'securePassword' contains just 'String'
val securePassword = Password("Don't try this in production") 

Это главная особенность встраиваемых классов, которая и определила название встраиваемый: данные класса встраиваются в места его использования (подобно тому, как содержимое встраиваемых функций встраивается в места вызова).

Члены

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

@JvmInline
value class Person(private val fullName: String) {
    init {
        require(fullName.isNotEmpty()) {
            "Full name shouldn't be empty"
        }
    }

    constructor(firstName: String, lastName: String) : this("$firstName $lastName") {
        require(lastName.isNotBlank()) {
            "Last name shouldn't be empty"
        }
    }

    val length: Int
        get() = fullName.length

    fun greet() {
        println("Hello, $fullName")
    }
}

fun main() {
    val name1 = Person("Kotlin", "Mascot")
    val name2 = Person("Kodee")
    name1.greet() // the `greet()` function is called as a static method
    println(name2.length) // property getter is called as a static method
}

Свойства встраиваемых классов не могут иметь поля для хранения значения. Для них можно использовать только простые вычисляемые свойства (без свойств lateinit /делегированных свойств).

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

Встраиваемые классы могут наследовать интерфейсы:

interface Printable {
    fun prettyPrint(): String
}

@JvmInline
value class Name(val s: String) : Printable {
    override fun prettyPrint(): String = "Let's $s!"
}

fun main() {
    val name = Name("Kotlin")
    println(name.prettyPrint()) // Still called as a static method
}

Встраиваемым классам запрещено участвовать в иерархии классов. Это означает, что встраиваемые классы не могут наследовать другие классы и всегда являются final.

Представление

В сгенерированном коде компилятор Kotlin сохраняет обёртку для каждого встраиваемого класса. Во время выполнения экземпляры встраиваемого класса могут представляться либо как обёртки, либо как базовый тип. Это похоже на то, как Int могут быть представлены либо как примитивный int, либо как обёртка Integer.

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

interface I

@JvmInline
value class Foo(val i: Int) : I

fun asInline(f: Foo) {}
fun <T> asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}

fun <T> id(x: T): T = x

fun main() {
    val f = Foo(42) 
    
    asInline(f)    // unboxed: used as Foo itself
    asGeneric(f)   // boxed: used as generic type T
    asInterface(f) // boxed: used as type I
    asNullable(f)  // boxed: used as Foo?, which is different from Foo
    
    // below, 'f' first is boxed (while being passed to 'id') and then unboxed (when returned from 'id') 
    // In the end, 'c' contains unboxed representation (just '42'), as 'f' 
    val c = id(f)  
}

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

Базовый тип встраиваемых классов также может быть обобщённым типовым параметром. В этом случае компилятор преобразует его в Any? или, в общем случае, в верхнюю границу типового параметра.

@JvmInline
value class UserId<T>(val value: T)

fun compute(s: UserId<String>) {} // compiler generates fun compute-<hashcode>(s: Any?)

Манипуляция именами

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

@JvmInline
value class UInt(val x: Int)

// Represented as 'public final void compute(int x)' on the JVM
fun compute(x: Int) { }

// Also represented as 'public final void compute(int x)' on the JVM!
fun compute(x: UInt) { }

Чтобы избежать подобных проблем, функции, использующие встраиваемые классы, манглируются: к имени функции добавляется стабильный хеш-код. Поэтому fun compute(x: UInt) будет представлено как public final void compute-<hashcode>(int x), что устраняет проблему конфликта.

Вызов из кода Java

Из кода Java можно вызывать функции, принимающие встраиваемые классы. Для этого необходимо вручную отключить манглирование: добавьте аннотацию @JvmName перед объявлением функции:

@JvmInline
value class UInt(val x: Int)

fun compute(x: Int) { }

@JvmName("computeUInt")
fun compute(x: UInt) { }

По умолчанию Kotlin компилирует встраиваемые классы с использованием неупакованных представлений, из-за чего к ним сложно обращаться из Java. О том, как компилировать встраиваемые классы в упакованные представления, доступные из Java, читайте в руководстве Вызов Kotlin из Java.

Встраиваемые классы и псевдонимы типов

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

Однако ключевое различие заключается в том, что псевдонимы типов совместимы при присваивании со своим базовым типом (и с другими псевдонимами типов с тем же базовым типом), а встраиваемые классы — нет.

Иными словами, встраиваемые классы вводят действительно новый тип, в отличие от псевдонимов типов, которые лишь вводят альтернативное имя (псевдоним) для существующего типа:

typealias NameTypeAlias = String

@JvmInline
value class NameInlineClass(val s: String)

fun acceptString(s: String) {}
fun acceptNameTypeAlias(n: NameTypeAlias) {}
fun acceptNameInlineClass(p: NameInlineClass) {}

fun main() {
    val nameAlias: NameTypeAlias = ""
    val nameInlineClass: NameInlineClass = NameInlineClass("")
    val string: String = ""

    acceptString(nameAlias) // OK: pass alias instead of underlying type
    acceptString(nameInlineClass) // Not OK: can't pass inline class instead of underlying type

    // And vice versa:
    acceptNameTypeAlias(string) // OK: pass underlying type instead of alias
    acceptNameInlineClass(string) // Not OK: can't pass underlying type instead of inline class
}

Встраиваемые классы и делегирование

Реализация через делегирование встроенному значению встраиваемого класса разрешена для интерфейсов:

interface MyInterface {
    fun bar()
    fun foo() = "foo"
}

@JvmInline
value class MyInterfaceWrapper(val myInterface: MyInterface) : MyInterface by myInterface

fun main() {
    val my = MyInterfaceWrapper(object : MyInterface {
        override fun bar() {
            // body
        }
    })
    println(my.foo()) // prints "foo"
}
12 августа 2026 г.
Классы-перечисленияВложенные и внутренние классы

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

Spec-Zone.ru

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