В чем разница между object и companion object в Kotlin? Примеры
Ключевое отличие: object — это синглтон, companion object — статический член класса
В Kotlin object и companion object решают разные задачи, хотя на первый взгляд выглядят похоже. Давайте разберем каждый случай.
1. Вложенный object — это полноценный синглтон
Когда вы пишете object внутри класса, вы создаете независимый объект-одиночку, который привязан к внешнему классу только логически (как вложенный класс). У него есть собственное состояние, и он может реализовывать интерфейсы:
class DatabaseManager {
object Config : Serializable {
var HOST = "localhost"
var PORT = 5432
fun printConfig() {
println("$HOST:$PORT")
}
}
fun connect() {
println("Connecting to ${Config.HOST}:${Config.PORT}")
}
}
// Использование:
DatabaseManager.Config.HOST = "192.168.1.1"
DatabaseManager.Config.printConfig() // 192.168.1.1:5432
// Можно передавать как объект
val config: Serializable = DatabaseManager.Config
2. companion object — это статический член класса
companion object — это аналог static из Java. Он существует в единственном экземпляре на весь класс и может содержать фабричные методы, константы или вспомогательные функции. Но не может наследоваться или сериализоваться как самостоятельная сущность:
class DatabaseManager private constructor(val config: Config) {
companion object {
const val DEFAULT_PORT = 5432
private var instance: DatabaseManager? = null
fun create(config: Config): DatabaseManager {
if (instance == null) {
instance = DatabaseManager(config)
}
return instance!!
}
}
}
// Использование:
val db = DatabaseManager.create(Config())
println(DatabaseManager.DEFAULT_PORT) // 5432
// Нельзя передать companion object как объект
// val ref: Any = DatabaseManager.Companion // ❌ Ошибка! Нельзя получить ссылку
Основные различия в таблице
- Создание экземпляра:
objectсоздается при первом обращении,companion object— при загрузке класса - Наследование:
objectможет наследовать классы/интерфейсы,companion object— нет (только встроенное наследование от Any) - Сериализация:
objectможно сериализовать,companion object— нет (это просто контейнер статики) - Доступ из Java:
object— черезDatabaseManager.Config.INSTANCE,companion object— черезDatabaseManager.Companionили напрямую, если есть аннотация@JvmStatic - Именование:
objectтребует явного имени,companion objectможет быть безымянным (тогда имя по умолчанию —Companion)
Когда что использовать?
- object внутри класса — когда нужен вложенный синглтон с собственным состоянием, который может реализовывать интерфейсы (например, конфигурация, слушатели событий)
- companion object — для статических методов/констант, фабричных методов, служебных функций, не зависящих от экземпляра класса
Пример правильного использования:
class UserRepository {
// Вложенный object для конфигурации
object Config {
var apiUrl = "https://api.example.com"
var timeout = 5000L
}
// companion object для фабричного метода
companion object {
fun createDefault(): UserRepository {
return UserRepository()
}
}
}
Итог: object — это самостоятельный объект, живущий внутри класса, а companion object — это синтаксический сахар для статических членов. Выбирайте по задаче!
Дополнение: тонкости с Java-совместимостью
Если вы пишете код, который будет вызываться из Java, разница становится критичной.
Для companion object можно использовать аннотацию @JvmStatic, чтобы методы вызывались как статические напрямую:
class MyClass {
companion object {
@JvmStatic
fun hello() = "Hello from companion"
}
}
// Из Java: MyClass.hello() — без .Companion
Для вложенного object из Java придется обращаться через .INSTANCE:
class MyClass {
object MyObject {
fun hello() = "Hello from object"
}
}
// Из Java: MyClass.MyObject.INSTANCE.hello()
Также companion object удобен для реализации паттерна "Одиночка" с ленивой инициализацией через lazy:
class Singleton private constructor() {
companion object {
val instance: Singleton by lazy { Singleton() }
}
}
Совет: если вы не уверены, используйте companion object для статических методов/констант, а object — когда нужен именно объект-синглтон с поведением.