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

Ответ

Оба используются для реализации паттерна Singleton (Одиночка), но с разной областью видимости и назначением.

object (Объект объявления)

  • Что это: Самостоятельный синглтон. Объявление object одновременно объявляет класс и создает его единственный экземпляр с именем объекта.
  • Область видимости: Глобальная (в пределах своего пакета и видимости).
  • Основное назначение: Утилитные классы, фасады, репозитории, хранилища глобального состояния.
  • Пример:
    object AppConfig {
        const val API_URL = "https://api.example.com"
        val appVersion: String by lazy { loadVersionFromFile() }
        fun log(message: String) { println("[App]: $message") }
    }
    // Использование
    val url = AppConfig.API_URL
    AppConfig.log("Started")

companion object (Спутник)

  • Что это: Синглтон, привязанный к классу, в котором он объявлен. Аналог статических членов в Java, но более мощный.
  • Область видимости: Внутри класса, через имя класса (ClassName.Companion или просто ClassName для членов спутника).
  • Основное назначение:
    1. Фабричные методы (альтернатива конструкторам).
    2. Константы, специфичные для класса.
    3. Может реализовывать интерфейсы.
    4. Может иметь имя (например, companion object Factory).
  • Пример:
    class User private constructor(val id: String) {
        companion object {
            // Фабричный метод
            fun createWithEmail(email: String): User {
                val id = generateIdFromEmail(email)
                return User(id)
            }
            // Константа
            const val DEFAULT_NAME = "Guest"
        }
    }
    // Использование
    val user = User.createWithEmail("test@example.com")
    val name = User.DEFAULT_NAME
Ключевые различия: Признак object companion object
Связь с классом Самостоятельная сущность Вложен в класс, является его частью
Аналог в Java public static final класс с приватным конструктором Статические методы и поля внутри класса
Доступ к членам Через имя объекта (AppConfig.API_URL) Через имя внешнего класса (User.DEFAULT_NAME)
Наследование Может наследовать классы и реализовывать интерфейсы Может реализовывать интерфейсы

Ответ 18+ 🔞

Слушай, а вот этот твой вопрос про object и companion object — это ж классика, блядь! Прям как в анекдоте про двух близнецов, где один в глобальной видимости, а другой при мамке сидит. Ёпта, сейчас разжуем.

Ну смотри, оба эти придурка нужны, чтобы сделать Singleton, то есть такую сущность, которая в единственном экземпляре, как последняя бутылка пива в холодильнике в три часа ночи. Но разница — пиздец какая принципиальная!

object (Просто объект, сам по себе пацан)

  • Что это такое? Это, блядь, сразу и объявление класса, и создание его единственного экземпляра. Всё, готово, пользуйся. Как будто ты крикнул "Эй, Гриша!" — и он уже тут, нахуй.
  • Где он торчит? В глобальной видимости, как шишка на лбу. В пределах своего пакета его все видят.
  • Зачем он нужен? Для всяких утилит, конфигов, репозиториев — для всего, что должно быть одно на всю программу. Представь себе диспетчера такси, к которому все едут. Вот это он.
  • Пример, чтобы не быть мудаком:
    object AppConfig {
        const val API_URL = "https://api.example.com"
        val appVersion: String by lazy { loadVersionFromFile() } // Лениво, чтобы не грузиться раньше времени
        fun log(message: String) { println("[App]: $message") }
    }
    // Использование — элементарно, Ватсон!
    val url = AppConfig.API_URL
    AppConfig.log("Started")

companion object (Спутник, или "сынок при мамке")

  • Что это такое? Это синглтон, который прицеплен к конкретному классу. Он живёт внутри него, как таракан за плинтусом. Аналог статики из Java, только поумнее.
  • Где он торчит? Внутри класса. Доступ через имя класса, типа ИмяКласса.ИмяЧленаСпутника.
  • Зачем он нужен? Вот тут интереснее, ёпта!
    1. Фабричные методы. Когда конструкторы — говно, а тебе надо создавать объекты хитро.
    2. Константы класса. Не глобальные, а именно для этого класса.
    3. Может даже интерфейсы реализовывать, не лыком шит!
    4. Может иметь своё имя (например, companion object Loader), чтобы не быть анонимным пидором.
  • Пример, чтобы стало совсем ясно:
    class User private constructor(val id: String) { // Конструктор приватный, нахуй!
        companion object {
            // Фабричный метод — вот так правильно!
            fun createWithEmail(email: String): User {
                val id = generateIdFromEmail(email)
                return User(id) // А тут мы внутри, доступ есть!
            }
            // Константа, специфичная для юзера
            const val DEFAULT_NAME = "Guest"
        }
    }
    // Использование — через имя класса
    val user = User.createWithEmail("test@example.com") // Видишь? User.метод
    val name = User.DEFAULT_NAME
А теперь, блядь, главные различия, чтобы не путать: Признак object companion object
С кем живёт? Самодостаточный мужик, одиночка. Сынок, привязан к родительскому классу.
На что похож в Java? public static final класс с приватным конструктором. Просто статические методы и поля внутри класса.
Как до него достучаться? По имени объекта: AppConfig.что-то. Через имя внешнего класса: User.что-то.
Может ли наследоваться? Да, может и классы наследовать, и интерфейсы пихать. Только интерфейсы реализовывать, наследовать классы — низя.

Короче, если тебе нужна глобальная штука на весь проект — бери object. Если нужна статика, привязанная к логике конкретного класса (фабрики, константы) — твой выбор companion object. Всё просто, как три копейки, блядь!