Объектные выражения и объявления
Иногда вам нужно создать объект, который является незначительной модификацией какого-либо класса, не объявляя явно новый подкласс для него. Kotlin может справиться с этим с помощью объектных выражений и объектных объявлений.
Объектные выражения
Объектные выражения создают объекты анонимных классов, то есть классов, которые не объявлены явно с помощью class объявления. Такие классы полезны для одноразового использования. Вы можете определить их с нуля, унаследовать от существующих классов или реализовать интерфейсы. Экземпляры анонимных классов также называются анонимными объектами, потому что они определяются выражением, а не именем.
Создание анонимных объектов с нуля
Объектные выражения начинаются с ключевого слова object.
Если вам просто нужен объект, у которого нет никаких нетривиальных супертипов, запишите его члены в фигурные скобки после object:
fun main() {
//sampleStart
val helloWorld = object {
val hello = "Hello"
val world = "World"
// object expressions extend Any, so `override` is required on `toString()`
override fun toString() = "$hello $world"
}
//sampleEnd
print(helloWorld)
}
Наследование анонимных объектов от супертипов
Чтобы создать объект анонимного класса, который наследуется от некоторого типа (или типов), укажите этот тип после object и двоеточия (:). Затем реализуйте или переопределите члены этого класса так, как если бы вы унаследовали от него:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { /*...*/ }
override fun mouseEntered(e: MouseEvent) { /*...*/ }
})
Если супертип имеет конструктор, передайте соответствующие параметры конструктора в него. Несколько супертипов могут быть указаны как список, разделенный запятыми, после двоеточия:
open class A(x: Int) {
public open val y: Int = x
}
interface B { /*...*/ }
val ab: A = object : A(1), B {
override val y = 15
}
Использование анонимных объектов в качестве типов возвращаемых значений
Когда анонимный объект используется в качестве типа локального или приватного, но не встроенного объявления (функции или свойства), все его члены доступны через эту функцию или свойство:
class C {
private fun getObject() = object {
val x: String = "x"
}
fun printX() {
println(getObject().x)
}
}
Если эта функция или свойство являются общедоступными или приватными встроенными, ее фактический тип:
Anyесли у анонимного объекта нет объявленного супертипаОбъявленный супертип анонимного объекта, если существует ровно один такой тип
Явно объявленный тип, если объявлено более одного супертипа
Во всех этих случаях члены, добавленные в анонимный объект, недоступны. Переопределенные члены доступны, если они объявлены в фактическом типе функции или свойства:
interface A {
fun funFromA() {}
}
interface B
class C {
// The return type is Any. x is not accessible
fun getObject() = object {
val x: String = "x"
}
// The return type is A; x is not accessible
fun getObjectA() = object: A {
override fun funFromA() {}
val x: String = "x"
}
// The return type is B; funFromA() and x are not accessible
fun getObjectB(): B = object: A, B { // explicit return type is required
override fun funFromA() {}
val x: String = "x"
}
}
Доступ к переменным из анонимных объектов
Код в объектных выражениях может обращаться к переменным из окружающего контекста:
fun countClicks(window: JComponent) {
var clickCount = 0
var enterCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
override fun mouseEntered(e: MouseEvent) {
enterCount++
}
})
// ...
}
Объектные объявления
Шаблон синглтона может быть полезен в нескольких случаях, и Kotlin делает объявление синглтонов простым:
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider>
get() = // ...
}
Это называется объявлением объекта, и у него всегда есть имя после ключевого слова object. Как и объявление переменной, объявление объекта не является выражением и не может быть использовано в правой части оператора присваивания.
Инициализация объекта объявления выполняется потокобезопасно и при первом обращении.
Для ссылки на объект используйте его имя напрямую:
DataProviderManager.registerDataProvider(...)
Такие объекты могут иметь супертипы:
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { ... }
override fun mouseEntered(e: MouseEvent) { ... }
}
Объекты данных
При выводе простого object объявления на Kotlin вы заметите, что его строковое представление содержит как его имя, так и хеш-код объекта:
object MyObject
fun main() {
println(MyObject) // MyObject@1f32e575
}
Как и классы данных, вы можете пометить свое object объявление модификатором data, чтобы получить красиво отформатированное строковое представление, без необходимости вручную предоставлять реализацию для функции toString:
data object MyObject
fun main() {
println(MyObject) // MyObject
}
Иерархии запечатанных классов особенно хорошо подходят для data object объявлений, так как они позволяют сохранить симметрию с любыми классами данных, которые вы могли бы определить вместе с объектом:
sealed class ReadResult {
data class Number(val value: Int): ReadResult()
data class Text(val value: String): ReadResult()
data object EndOfFile: ReadResult()
}
fun main() {
println(ReadResult.Number(1)) // Number(value=1)
println(ReadResult.Text("Foo")) // Text(value=Foo)
println(ReadResult.EndOfFile) // EndOfFile
}
Вспомогательные объекты
Объявление объекта внутри класса может быть помечено ключевым словом companion:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
Члены вспомогательного объекта могут вызываться, просто используя имя класса в качестве квалификатора:
val instance = MyClass.create()
Имя вспомогательного объекта можно опустить, в этом случае будет использовано имя Companion:
class MyClass {
companion object { }
}
val x = MyClass.Companion
Члены класса могут обращаться к приватным членам соответствующего вспомогательного объекта.
Имя класса, используемого само по себе (а не в качестве квалификатора к другому имени), действует как ссылка на вспомогательный объект класса (названного или нет):
class MyClass1 {
companion object Named { }
}
val x = MyClass1
class MyClass2 {
companion object { }
}
val y = MyClass2
Обратите внимание, что хотя члены вспомогательных объектов выглядят как статические члены в других языках, во время выполнения они по-прежнему являются экземплярами реальных объектов и могут, например, реализовывать интерфейсы:
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
val f: Factory<MyClass> = MyClass
Однако в JVM члены вспомогательных объектов могут быть сгенерированы как реальные статические методы и поля, если вы используете аннотацию @JvmStatic. Подробнее см. раздел Взаимодействие с Java.
Семантические различия между объектами выражениями и объявлениями
Существует одно важное семантическое различие между объектами выражениями и объектами объявлениями:
Объектные выражения выполняются (и инициализируются) немедленно, где они используются.
Объектные объявления инициализируются лениво, при первом обращении.
Вспомогательный объект инициализируется при загрузке (разрешении) соответствующего класса, что соответствует семантике статического инициализатора Java.
© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/object-declarations.html