Kotlin. Ключевое слово object.
Ключевое слово object позволяет одновременно объявить класс и создать его экземпляр (или другими словами, объект). При этом использовать его можно по-разному:
- объявление объекта;
- реализация объекта-компаньона;
- запись объекта-выражения (также известен как анонимный объект и object expressions).
Объявление объекта
Наверняка вам приходилось хоть раз создавать такой класс, который должен существовать в одном экземпляре. Обычно это реализуется при помощи паттерна проектирования синглтон. В Kotlin же предлагается использовать объявление объекта, которое одновременно сочетает в себе и объявление класса, и создание его единственного экземпляра.
Объявляется объект при помощи ключевого слова object , после которого следует имя объекта.
1 2 3
object SomeObject
Как и класс, объект может обладать свойствами, функциями, блоками инициализации, может наследоваться от классов и реализовывать интерфейсы. Единственное, что не допускается — основные и вторичные конструкторы.
Объекты можно объявлять внутри класса. Такие объекты тоже существуют в единственном экземпляре, т.е. у любого экземпляра класса будет один и тот же экземпляр объекта.
1 2 3 4 5 6 7 8 9 10 11 12 13
class Someclass < . object SomeObject < fun create() < . >> > . val someClass = SomeClass.SomeObject.create()
Объект-компаньон (Companion Object)
Как упоминалось ранее, объекты можно объявлять внутри класса. При этом нет каких-либо ограничений по их количеству. Но лишь один объект можно пометить ключевым словом companion . Такому объекту можно не указывать имя, а к его компонентам обращаться через имя класса, в котором он находится.
1 2 3 4 5 6 7 8 9 10
class SomeClass < companion object < fun create() >> . val someClass = SomeClass.create()
Как правило объекты-компаньоны используются для объявления переменных и функций, к которым требуется обращаться без создания экземпляра класса. Либо для объявления констант. По сути они своего рода замена статическим членам класса (в отличие от Java, в Kotlin нет статики).
Объект-выражение
Объект-выражение — это выражение, которое “на ходу” создает анонимный объект, который в свою очередь является заменой анонимным внутренним классам в Java.
При разработке приложений анонимный объект чаще всего используется для реализации обработчика событий (клики по компонентам экрана).
1 2 3 4 5
button.setOnClickListener(object : View.OnClickListener < override fun onClick(v: View?) < . >>)
В данном примере создаётся объект, который реализует интерфейс View.OnClickListener и передаётся функции setOnClickListener() в качестве параметра. Этот параметр и является объектом-выражением.
Обратите внимание, что для объекта-выражения не указывается имя. Зачем оно ему, если объект просто передается функции в качестве параметра?
Если же объекту всё таки требуется имя, то его можно сохранить в переменной.
1 2 3
val listener = object : View.OnClickListener < override fun onClick(v: View?) < . >>
В объекте-выражении можно обращаться к переменным, которые находятся в той же функции, в которой был создан анонимный объект.
1 2 3 4 5 6 7 8 9
fun countClicks() < var count = 0 button.setOnClickListener(object : View.OnClickListener< override fun onClick(v: View?) < count++ >>) >
Анонимный объект может реализовывать несколько интерфейсов, тогда они перечисляются через запятую. А может и вовсе не реализовывать ни один.
1 2 3 4 5
val tree = object < var name = "Сосна" var description = "хвойное дерево с длинными иглами и округлыми шишками" >print("$ - $.")
Обратите внимание, что анонимные объекты не являются синглтонами. Каждый раз при выполнении объекта-выражения создаётся новый объект.
Полезные ссылки
Object Expressions and Declarations — официальная документация.
Анонимные объекты и объявление объектов — неофициальный перевод документации на русский язык.
Kotlin: статика, которой нет — статья об использовании статики в Kotlin. Есть немного про объекты — сравнение кода Java — Kotlin.
companion
Классы в Kotlin не могут иметь статических членов, ключевое слово static не входит в состав языка.
Можно пометить объект в классе ключевым словом companion вместе с другим ключевым словом object и обращаться к методам и свойствам объекта через имя содержащего его класса без явного указания имени объекта.
class Something < companion object < val age = 11 fun bar() < println("Companion object called") >> > Something.bar() // Companion object called println(Something.age)
Объект-компаньон имеет доступ ко всем членам класса и подходит для реализации шаблона «Фабрика».
Возвращаясь к примеру с val age, нужно заметить, что следует избегать такого кода. Лучше добавить к переменной const.
companion object
Этот способ работает для примитивных типов и строк (Int, Double, String. ).
Для объектов можно воспользоваться аннотацией @JvmField.
companion object
Можно дать идентификатор компаньону.
companion object MyObject < . >Something.MyObject.bar()
Анонимные объекты и объявление объектов
Иногда нам необходимо получить экземпляр некоторого класса с незначительной модификацией, желательно без написания нового подкласса. 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
Использование анонимных объектов в качестве возвращаемых типов значений
Когда анонимный объект используется в качестве типа 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 get() = // . >
Это называется объявлением объекта и всегда имеет приставку в виде ключевого слова object . Аналогично объявлению переменной, объявление объекта не является выражением и не может быть использовано в правой части оператора присваивания.
Инициализация объявления объекта потокобезопасна и выполняется при первом доступе.
Для непосредственной ссылки на объект используется его имя.
DataProviderManager.registerDataProvider( /*. */ )
Подобные объекты могут иметь супертипы:
object DefaultListener : MouseAdapter() < override fun mouseClicked(e: MouseEvent) < /*. */ >override fun mouseEntered(e: MouseEvent) < /*. */ >>
Object declarations can’t be local (that is, they can’t be nested directly inside a function), but they can be nested > into other object declarations or non-inner classes. —>
Объявление объекта не может иметь локальный характер (т.е. быть вложенным непосредственно в функцию), но может быть вложено в объявление другого объекта или какого-либо невложенного класса.
Вспомогательные объекты
Объявление объекта внутри класса может быть отмечено ключевым словом 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 < fun create(): T >class MyClass < companion object : Factory < override fun create(): MyClass = MyClass() >> val f: Factory = MyClass
Однако в JVM вы можете статически генерировать методы вспомогательных объектов и полей, используя аннотацию @JvmStatic . См. Совместимость с Java.
Семантическое различие между анонимным объектом и декларируемым объектом
Существует только одно смысловое различие между этими двумя понятиями:
- анонимный объект инициализируется непосредственно при использовании;
- декларированный объект инициализируется лениво, в момент первого к нему доступа;
- вспомогательный объект инициализируется в момент, когда класс, к которому он относится, загружен и семантически совпадает со статическим инициализатором Java.
© 2015—2023 Open Source Community
Companion object в Kotlin
Только перехожу с Java на Kotlin. Возник вопрос, ответ на который, к сожалению, я не смог найти. Существует некий абстрактный класс с несколькими абстрактными переменными, на который потом будут ложиться десятки тестов. Тестирую с помощью JUnit. Методы аннотированные @BeforeClass и @AfterClass обязаны быть статичными, и я вижу только один способ разрешения пробелемы: запихнуть методы внутрь companion object , где можно использовать @JvmStatic , но при этом, в методе @BeforeClass вызывается абстрактная переменная, задаваемая каждой реализацией отдельно. Соответсвенно, каким образом можно обратиться к переменной из внешнего класса? Или может быть есть другой способ разрешения такой задачи? Код:
abstract class TemplateConfig < abstract val template : String? companion object < lateinit var h: Handle @BeforeClass @JvmStatic fun setUp() < h = dbi.value.open() //Здесь используется абстрактная переменная // //if (template != null) < // h.createStatement(template).execute() //>> @AfterClass @JvmStatic fun tearDown() < h.close() >// > >
Отслеживать
задан 19 авг 2016 в 8:16
Янислав Корнев Янислав Корнев
399 1 1 серебряный знак 17 17 бронзовых знаков
19 авг 2016 в 8:25
@rjhdby Спасибо, конечно, но это я и сам находил, и на мой вопрос это никак не отвечает.
19 авг 2016 в 8:48
В целом ответы сходятся к «Don’t put the BeforeClass on the abstract class. Call it from each subclass.»
19 авг 2016 в 9:58
@rjhdby похоже, что так. Альтернатив нет? Ибо писать одно и тоже в двух десятках тестовых классов не очень удобно.