Объявления и выражения объектов
В Kotlin объекты позволяют определить класс и создать его экземпляр за один шаг. Это полезно, когда вам нужен повторно используемый экземпляр-одиночка или одноразовый объект. Для этих сценариев Kotlin предлагает два основных подхода: объявления объектов для создания одиночек и выражения объектов для создания анонимных одноразовых объектов.
Объявления и выражения объектов лучше всего использовать в следующих случаях:
Использование одиночек для общих ресурсов: необходимо гарантировать, что во всём приложении существует только один экземпляр класса. Например, для управления пулом подключений к базе данных.
Создание фабричных методов: нужен удобный способ эффективно создавать экземпляры. Объекты-компаньоны позволяют определять функции и свойства уровня класса, связанные с классом, упрощая создание экземпляров и управление ими.
Временное изменение поведения существующего класса: нужно изменить поведение существующего класса без создания нового подкласса. Например, добавить объекту временную функциональность для выполнения конкретной операции.
Требуется типобезопасный дизайн: нужно однократно реализовать интерфейсы или абстрактные классы с помощью выражений объектов. Это может быть полезно, например, для обработчика нажатия кнопки.
Объявления объектов
В Kotlin можно создавать одиночные экземпляры объектов с помощью объявлений объектов, у которых всегда есть имя после ключевого слова object. Это позволяет определить класс и создать его экземпляр за один шаг, что удобно для реализации одиночек:
//sampleStart
// Declares a Singleton object to manage data providers
object DataProviderManager {
private val providers = mutableListOf<DataProvider>()
// Registers a new data provider
fun registerDataProvider(provider: DataProvider) {
providers.add(provider)
}
// Retrieves all registered data providers
val allDataProviders: Collection<DataProvider>
get() = providers
}
//sampleEnd
// Example data provider interface
interface DataProvider {
fun provideData(): String
}
// Example data provider implementation
class ExampleDataProvider : DataProvider {
override fun provideData(): String {
return "Example data"
}
}
fun main() {
// Creates an instance of ExampleDataProvider
val exampleProvider = ExampleDataProvider()
// To refer to the object, use its name directly
DataProviderManager.registerDataProvider(exampleProvider)
// Retrieves and prints all data providers
println(DataProviderManager.allDataProviders.map { it.provideData() })
// [Example data]
}
Чтобы обратиться к object, используйте его имя напрямую:
DataProviderManager.registerDataProvider(exampleProvider)
Объявления объектов также могут иметь суперклассы, подобно тому, как анонимные объекты могут наследовать существующие классы или реализовывать интерфейсы:
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { ... }
override fun mouseEntered(e: MouseEvent) { ... }
}
Как и объявления переменных, объявления объектов не являются выражениями, поэтому их нельзя использовать в правой части оператора присваивания:
// Syntax error: An object expression cannot bind a name.
val myObject = object MySingleton {
val name = "Singleton"
}
Объявления объектов не могут быть локальными, то есть их нельзя непосредственно вложить в функцию. Однако их можно вложить в другие объявления объектов или в классы, не являющиеся внутренними.
Объекты данных
При выводе обычного объявления объекта в Kotlin его строковое представление содержит и имя объекта, и хеш object:
object MyObject
fun main() {
println(MyObject)
// MyObject@hashcode
}
Однако, пометив объявление объекта модификатором data, вы можете указать компилятору возвращать фактическое имя объекта при вызове toString(), как это происходит с классами данных:
data object MyDataObject {
val number: Int = 3
}
fun main() {
println(MyDataObject)
// MyDataObject
}
Кроме того, компилятор генерирует несколько функций для data object:
toString()возвращает имя объекта данных-
equals()/hashCode()обеспечивают проверку на равенство и работу коллекций на основе хеширования
Функция equals() для data object гарантирует, что все объекты типа data object считаются равными. В большинстве случаев во время выполнения будет существовать только один экземпляр data object, поскольку data object объявляет одиночку. Однако в редком случае, когда во время выполнения создаётся другой объект того же типа (например, с помощью платформенной рефлексии через java.lang.reflect или библиотеки сериализации JVM, использующей этот API внутри), это гарантирует, что объекты будут считаться равными.
import java.lang.reflect.Constructor
data object MySingleton
fun main() {
val evilTwin = createInstanceViaReflection()
println(MySingleton)
// MySingleton
println(evilTwin)
// MySingleton
// Even when a library forcefully creates a second instance of MySingleton,
// its equals() function returns true:
println(MySingleton == evilTwin)
// true
// Don't compare data objects using ===
println(MySingleton === evilTwin)
// false
}
fun createInstanceViaReflection(): MySingleton {
// Kotlin reflection does not permit the instantiation of data objects.
// This creates a new MySingleton instance "by force" (using Java platform reflection)
// Don't do this yourself!
return (MySingleton.javaClass.declaredConstructors[0].apply { isAccessible = true } as Constructor<MySingleton>).newInstance()
}
Поведение сгенерированной функции hashCode() согласовано с поведением функции equals(), поэтому все экземпляры data object во время выполнения имеют одинаковый хеш-код.
Различия между объектами данных и классами данных
Хотя объявления data object и data class часто используются вместе и имеют некоторые сходства, для data object не генерируются некоторые функции:
Нет функции
copy(). Поскольку объявлениеdata objectпредназначено для использования в качестве одиночки, функцияcopy()не генерируется. Одиночки ограничивают создание экземпляров класса одним экземпляром, а возможность создавать копии нарушила бы это ограничение.Нет функции
componentN(). В отличие отdata class,data objectне содержит свойств с данными. Поскольку деструктуризация такого объекта без свойств с данными не имеет смысла, функцииcomponentN()не генерируются.
Использование объектов данных с запечатанными иерархиями
Объявления объектов данных особенно полезны для запечатанных иерархий, таких как запечатанные классы или запечатанные интерфейсы. Они позволяют сохранить симметрию с классами данных, которые могли быть объявлены вместе с объектом.
В этом примере объявление EndOfFile как data object вместо обычного object означает, что для него будет доступна функция toString() без необходимости переопределять её вручную:
sealed interface ReadResult
data class Number(val number: Int) : ReadResult
data class Text(val text: String) : ReadResult
data object EndOfFile : ReadResult
fun main() {
println(Number(7))
// Number(number=7)
println(EndOfFile)
// EndOfFile
}
Объекты-компаньоны
Объекты-компаньоны позволяют определять функции и свойства уровня класса. Это упрощает создание фабричных методов, хранение констант и доступ к общим вспомогательным средствам.
Объявление объекта внутри класса можно пометить ключевым словом companion:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
Вызывать члены companion object можно просто по имени класса:
class User(val name: String) {
// Defines a companion object that acts as a factory for creating User instances
companion object Factory {
fun create(name: String): User = User(name)
}
}
fun main(){
// Calls the companion object's factory method using the class name as the qualifier.
// Creates a new User instance
val userInstance = User.create("John Doe")
println(userInstance.name)
// John Doe
}
Имя companion object можно опустить — в этом случае используется имя Companion:
class User(val name: String) {
// Defines a companion object without a name
companion object { }
}
// Accesses the companion object
val companionUser = User.Companion
Члены класса могут обращаться к членам private соответствующего companion object:
class User(val name: String) {
companion object {
private val defaultGreeting = "Hello"
}
fun sayHi() {
println(defaultGreeting)
}
}
User("Nick").sayHi()
// Hello
Если использовать имя класса само по себе, оно будет ссылкой на объект-компаньон этого класса независимо от того, задано ли имя объекту-компаньону:
//sampleStart
class User1 {
// Defines a named companion object
companion object Named {
fun show(): String = "User1's Named Companion Object"
}
}
// References the companion object of User1 using the class name
val reference1 = User1
class User2 {
// Defines an unnamed companion object
companion object {
fun show(): String = "User2's Companion Object"
}
}
// References the companion object of User2 using the class name
val reference2 = User2
//sampleEnd
fun main() {
// Calls the show() function from the companion object of User1
println(reference1.show())
// User1's Named Companion Object
// Calls the show() function from the companion object of User2
println(reference2.show())
// User2's Companion Object
}
Хотя члены объектов-компаньонов в Kotlin похожи на статические члены других языков, на самом деле это члены экземпляра объекта-компаньона, то есть они принадлежат самому объекту. Благодаря этому объекты-компаньоны могут реализовывать интерфейсы:
interface Factory<T> {
fun create(name: String): T
}
class User(val name: String) {
// Defines a companion object that implements the Factory interface
companion object : Factory<User> {
override fun create(name: String): User = User(name)
}
}
fun main() {
// Uses the companion object as a Factory
val userFactory: Factory<User> = User
val newUser = userFactory.create("Example User")
println(newUser.name)
// Example User
}
Однако в JVM члены объектов-компаньонов можно сгенерировать как настоящие статические методы и поля, используя аннотацию @JvmStatic. Подробнее см. раздел Взаимодействие с Java.
Выражения объектов
Выражения объектов объявляют класс и создают его экземпляр, но не задают имя ни классу, ни экземпляру. Такие классы подходят для однократного использования. Их можно создавать с нуля, наследовать от существующих классов или использовать для реализации интерфейсов. Экземпляры таких классов также называются анонимными объектами, поскольку они определяются выражением, а не именем.
Создание анонимных объектов с нуля
Выражения объектов начинаются с ключевого слова object.
Если объект не наследует классы и не реализует интерфейсы, его члены можно определить непосредственно в фигурных скобках после ключевого слова object:
fun main() {
//sampleStart
val helloWorld = object {
val hello = "Hello"
val world = "World"
// Object expressions extend the Any class, which already has a toString() function,
// so it must be overridden
override fun toString() = "$hello $world"
}
print(helloWorld)
// Hello World
//sampleEnd
}
Наследование анонимных объектов от супертипов
Чтобы создать анонимный объект, наследующий один или несколько типов, укажите этот тип после object и двоеточия :. Затем реализуйте или переопределите члены этого класса так же, как при наследовании от него:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { /*...*/ }
override fun mouseEntered(e: MouseEvent) { /*...*/ }
})
Если у суперкласса есть конструктор, передайте ему соответствующие параметры конструктора. После двоеточия можно указать несколько супертипов, разделив их запятыми:
//sampleStart
// Creates an open class BankAccount with a balance property
open class BankAccount(initialBalance: Int) {
open val balance: Int = initialBalance
}
// Defines an interface Transaction with an execute() function
interface Transaction {
fun execute()
}
// A function to perform a special transaction on a BankAccount
fun specialTransaction(account: BankAccount) {
// Creates an anonymous object that inherits from the BankAccount class and implements the Transaction interface
// The balance of the provided account is passed to the BankAccount superclass constructor
val temporaryAccount = object : BankAccount(account.balance), Transaction {
override val balance = account.balance + 500 // Temporary bonus
// Implements the execute() function from the Transaction interface
override fun execute() {
println("Executing special transaction. New balance is $balance.")
}
}
// Executes the transaction
temporaryAccount.execute()
}
//sampleEnd
fun main() {
// Creates a BankAccount with an initial balance of 1000
val myAccount = BankAccount(1000)
// Performs a special transaction on the created account
specialTransaction(myAccount)
// Executing special transaction. New balance is 1500.
}
Использование анонимных объектов в качестве типов возвращаемых значений и значений
Если вернуть анонимный объект из локальной функции или функции или свойства с private, все члены этого анонимного объекта будут доступны через эту функцию или свойство:
//sampleStart
class UserPreferences {
private fun getPreferences() = object {
val theme: String = "Dark"
val fontSize: Int = 14
}
fun printPreferences() {
val preferences = getPreferences()
println("Theme: ${preferences.theme}, Font Size: ${preferences.fontSize}")
}
}
//sampleEnd
fun main() {
val userPreferences = UserPreferences()
userPreferences.printPreferences()
// Theme: Dark, Font Size: 14
}
Это позволяет возвращать анонимный объект с определёнными свойствами и тем самым просто инкапсулировать данные или поведение без создания отдельного класса.
Если функция или свойство, возвращающие анонимный объект, имеют видимость public, protected или internal, их фактический тип будет следующим:
Any, если у анонимного объекта не объявлен супертип.Объявленный супертип анонимного объекта, если такой тип ровно один.
Явно объявленный тип, если объявлено несколько супертипов.
Во всех этих случаях члены, добавленные в анонимный объект, недоступны. Переопределённые члены доступны, если они объявлены в фактическом типе функции или свойства. Например:
//sampleStart
interface Notification {
// Declares notifyUser() in the Notification interface
fun notifyUser()
}
interface DetailedNotification
class NotificationManager {
// The return type is Any. The message property is not accessible.
// When the return type is Any, only members of the Any class are accessible.
fun getNotification() = object {
val message: String = "General notification"
}
// The return type is Notification because the anonymous object implements only one interface
// The notifyUser() function is accessible because it is part of the Notification interface
// The message property is not accessible because it is not declared in the Notification interface
fun getEmailNotification() = object : Notification {
override fun notifyUser() {
println("Sending email notification")
}
val message: String = "You've got mail!"
}
// The return type is DetailedNotification. The notifyUser() function and the message property are not accessible
// Only members declared in the DetailedNotification interface are accessible
fun getDetailedNotification(): DetailedNotification = object : Notification, DetailedNotification {
override fun notifyUser() {
println("Sending detailed notification")
}
val message: String = "Detailed message content"
}
}
//sampleEnd
fun main() {
// This produces no output
val notificationManager = NotificationManager()
// The message property is not accessible here because the return type is Any
// This produces no output
val notification = notificationManager.getNotification()
// The notifyUser() function is accessible
// The message property is not accessible here because the return type is Notification
val emailNotification = notificationManager.getEmailNotification()
emailNotification.notifyUser()
// Sending email notification
// The notifyUser() function and message property are not accessible here because the return type is DetailedNotification
// This produces no output
val detailedNotification = notificationManager.getDetailedNotification()
}
Доступ к переменным из анонимных объектов
Код внутри тела выражений объектов может обращаться к переменным из внешней области видимости:
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
fun countClicks(window: JComponent) {
var clickCount = 0
var enterCount = 0
// MouseAdapter provides default implementations for mouse event functions
// Simulates MouseAdapter handling mouse events
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
override fun mouseEntered(e: MouseEvent) {
enterCount++
}
})
// The clickCount and enterCount variables are accessible within the object expression
}
Различия в поведении объявлений и выражений объектов
Поведение объявлений и выражений объектов при инициализации различается:
Выражения объектов выполняются (и инициализируются) немедленно в месте их использования.
Объявления объектов инициализируются лениво при первом обращении к ним.
Объект-компаньон инициализируется при загрузке (разрешении) соответствующего класса, что соответствует семантике статического инициализатора Java.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/object-declarations.html