Встроенные классы
Встроенные классы доступны только начиная с Kotlin 1.3 и на данный момент находятся в стадии Альфа. Подробнее см. ниже
Иногда для бизнес-логики необходимо создать оболочку вокруг некоторого типа. Однако это вносит накладные расходы во время выполнения из-за дополнительных выделений памяти. Более того, если обернутый тип является примитивным, то производительность страдает из-за того, что примитивные типы обычно сильно оптимизированы во время выполнения, в то время как их оболочки не получают никакого специального обращения.
Для решения таких проблем Kotlin вводит специальный вид класса, называемый inline class, который объявляется путем размещения модификатора inline перед именем класса:
inline class Password(val value: String)
Встроенный класс должен иметь единственное свойство, инициализированное в первичном конструкторе. Во время выполнения экземпляры встроенного класса будут представлены с помощью этого единственного свойства (см. подробности о представлении во время выполнения ниже):
// No actual instantiation of class 'Password' happens
// At runtime 'securePassword' contains just 'String'
val securePassword = Password("Don't try this in production")
Это основная особенность встроенных классов, которая вдохновила название «встроенный»: данные класса «встраиваются» в его использования (аналогично тому, как содержимое встроенных функций встраивается в места вызова).
Члены
Встроенные классы поддерживают некоторую функциональность обычных классов. В частности, они могут объявлять свойства и функции:
inline class Name(val s: String) {
val length: Int
get() = s.length
fun greet() {
println("Hello, $s")
}
}
fun main() {
val name = Name("Kotlin")
name.greet() // method `greet` is called as a static method
println(name.length) // property getter is called as a static method
}
Однако существуют некоторые ограничения для членов встроенного класса:
- встроенные классы не могут иметь блоков init
- свойства встроенных классов не могут иметь вспомогательных полей
- отсюда следует, что встроенные классы могут иметь только простые вычисляемые свойства (нет свойств lateinit/делегированных)
Наследование
Встроенные классы могут наследовать от интерфейсов:
interface Printable {
fun prettyPrint(): String
}
inline 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
inline 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)
}
Поскольку встроенные классы могут быть представлены как базовым значением, так и оболочкой, сравнение по ссылке для них бесполезно и поэтому запрещено.
Обработка имён
Поскольку встроенные классы компилируются в их базовый тип, это может привести к различным непонятным ошибкам, например, к неожиданным конфликтам подписи платформы:
inline 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.
Встроенные классы против псевдонимов типов
На первый взгляд встроенные классы могут показаться очень похожими на псевдонимы типов. Действительно, оба, кажется, вводят новый тип, и оба будут представлены как базовый тип во время выполнения.
Однако ключевое различие состоит в том, что псевдонимы типов совместимы по присваиванию со своим базовым типом (и с другими псевдонимами типов с тем же базовым типом), в то время как встроенные классы — нет.
Другими словами, встроенные классы вводят новый тип, в отличие от псевдонимов типов, которые только вводят альтернативное имя (псевдоним) для существующего типа:
typealias NameTypeAlias = String
inline 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
}
Статус альфа-версии встроенных классов
Разработка встроенных классов находится в стадии Альфа, что означает, что гарантии совместимости для будущих версий не предоставляются. При использовании встроенных классов в Kotlin 1.3 и выше будет выводиться предупреждение, указывающее на то, что эта функция не является стабильной.
Чтобы удалить предупреждение, необходимо включить использование этой функции, передав аргумент компилятора -Xinline-classes.
Включение встроенных классов в Gradle
compileKotlin {
kotlinOptions.freeCompilerArgs += ["-Xinline-classes"]
}
tasks.withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs += "-Xinline-classes"
}
См. Параметры компилятора в Gradle для получения дополнительных сведений. Для проектов с многоплатформенной разработкой см. настройки языка.
Включение встроенных классов в Maven
<configuration>
<args>
<arg>-Xinline-classes</arg>
</args>
</configuration>
См. Параметры компилятора в Maven для получения дополнительных сведений.
Дальнейшее обсуждение
См. это предложение по языку для встроенных классов для получения дополнительной технической информации и обсуждения.
© 2010–2020 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/reference/inline-classes.html