Анонимные объекты и объявление объектов
Иногда нам необходимо получить экземпляр некоторого класса с незначительной модификацией, желательно без написания нового подкласса. Kotlin справляется с этим с помощью объявления объектов и анонимных объектов.
Анонимные объекты (ориг.: Object expressions)
Объекты анонимных классов, т.е. классов, которые явно не объявлены с помощью class
, полезны для одноразового
использования. Вы можете объявить их с нуля, наследовать от существующих классов или реализовать интерфейсы. Экземпляры
анонимных классов также могут называться анонимными объектами, потому что они объявляются выражением, а не именем.
Создание анонимных объектов с нуля
Анонимный объект начинается с ключевого слова object
.
Если вам просто нужен объект, у которого нет нетривиальных супертипов, перечислите его члены в фигурных скобках после object
.
val helloWorld = object {
val hello = "Hello"
val world = "World"
// тип анонимных объектов - Any, поэтому `override` необходим в `toString()`
override fun toString() = "$hello $world"
}
Наследование анонимных объектов от супертипов
Для того чтобы создать объект анонимного класса, который наследуется от какого-то типа (типов), укажите этот тип после
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
}
Использование анонимных объектов в качестве возвращаемых типов значений
Когда анонимный объект используется в качестве типа local или private, но не встроенного объявления (функции или свойства), все его члены доступны через эту функцию или свойство.
class C {
private fun getObject() = object {
val x: String = "x"
}
fun printX() {
println(getObject().x)
}
}
Если эта функция или свойство маркированы как public или встроенный private, то их тип:
Any
, если анонимный объект не имеет объявленного супертипа;- Объявленный супертип анонимного объекта, если существует ровно один такой тип;
- Явно объявленный тип, если существует более одного объявленного супертипа.
Во всех этих случаях члены, добавленные в анонимный объект, недоступны. Переопределенные члены доступны, если они объявлены в фактическом типе функции или свойства.
interface A {
fun funFromA() {}
}
interface B
class C {
// Возвращаемый тип Any. x недоступен
fun getObject() = object {
val x: String = "x"
}
// Возвращаемый тип A; x недоступен
fun getObjectA() = object: A {
override fun funFromA() {}
val x: String = "x"
}
// Возвращаемый тип B; funFromA() и x недоступны
fun getObjectB(): B = object: A, B { // требуется явный тип возвращаемого значения
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++
}
})
// ...
}
Объявления объектов (ориг.: Object declarations)
Синглтон - очень полезный паттерн программирования, и 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) { /*...*/ }
}
Объявление объекта не может иметь локальный характер (т.е. быть вложенным непосредственно в функцию), но может быть вложено в объявление другого объекта или какого-либо невложенного класса.
Вспомогательные объекты
Объявление объекта внутри класса может быть отмечено ключевым словом companion
.
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
Для вызова членов такого companion
объекта используется имя класса.
val instance = MyClass.create()
Необязательно указывать имя вспомогательного объекта. Тогда он будет назван Companion
.
class MyClass {
companion object { }
}
val x = MyClass.Companion
Члены класса могут получить доступ к private
членам соответствующего вспомогательного объекта.
Имя класса, используемого не в качестве определителя другого имени, действует как ссылка на вспомогательный объект класса (независимо от того, именован он или нет).
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.