Avatar ·

В чем разница между object и companion object в Kotlin? Примеры

Всем привет! Я начал изучать Kotlin и никак не могу понять разницу между обычным object внутри класса и companion object. Допустим, я пишу простой класс для работы с базой данных: ```kotlin class DatabaseManager { object Config { val HOST = "localhost" val PORT = 5432 } companion object { fun create(): DatabaseManager = DatabaseManager() } } ``` Я вижу, что оба варианта создают что-то похожее на статические члены из Java. Но зачем тогда два разных механизма? Когда нужно использовать object, а когда companion object? Может быть, есть какие-то тонкости с наследованием, сериализацией или доступом из Java? Буду благодарен за подробное объяснение с примерами. <<>>

Ключевое отличие: 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 — когда нужен именно объект-синглтон с поведением.