← Назад к вопросам

Когда инициализируется companion object?

2.0 Middle🔥 121 комментариев
#Kotlin основы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Когда инициализируется companion object

companion object в Kotlin — это объект, который инициализируется ленивым образом (lazy), но существует в единственном экземпляре (синглтон) для каждого класса. Момент его инициализации — важный момент для понимания производительности и поведения приложения.

Основной момент инициализации

companion object инициализируется при первом обращении к нему или к полям/методам класса.

class MyClass {
    companion object {
        init {
            Log.d("TAG", "companion object initialized")
        }
        
        const val CONSTANT = "value"
        
        fun staticMethod() {
            Log.d("TAG", "static method called")
        }
    }
}

// Инициализация companion object:
val constant = MyClass.CONSTANT        // Инициализация + возврат
MyClass.staticMethod()                 // Еще одна инициализация? Нет!
MyClass.staticMethod()                 // Уже инициализирован

Точный момент инициализации

class DatabaseConfig {
    companion object {
        init {
            println("DatabaseConfig companion object init")
        }
        
        val database: Database = Database()  // Тяжелая инициализация
        const val CONNECTION_POOL_SIZE = 10
    }
}

fun main() {
    println("1. Before accessing companion")
    
    // Момент инициализации companion object
    val poolSize = DatabaseConfig.CONNECTION_POOL_SIZE
    // Выведет: DatabaseConfig companion object init
    
    println("2. After accessing constant")
    
    // Повторное обращение — companion уже инициализирован
    DatabaseConfig.database.query("SELECT * FROM users")
    println("3. After database query")
}

// Вывод:
// 1. Before accessing companion
// DatabaseConfig companion object init
// 2. After accessing constant
// 3. After database query

Отличие const val и val в companion object

class Constants {
    companion object {
        const val CONST_VALUE = "const"     // Compile-time constant
        val VAL_VALUE = "val"              // Runtime value
        
        init {
            println("Companion object initialized")
        }
    }
}

fun main() {
    // const val подменяется compile time — companion НЕ инициализируется
    println(Constants.CONST_VALUE)
    println(Constants.CONST_VALUE)  // companion все еще не инициализирован!
    
    // val требует runtime значение — companion ИНИЦИАЛИЗИРУЕТСЯ
    println(Constants.VAL_VALUE)    // Выведет: Companion object initialized
    println(Constants.VAL_VALUE)    // companion уже инициализирован
}

Thread-safety

companion object инициализируется потокобезопасным образом благодаря JVM. Это означает, что даже при многопоточном доступе инициализация произойдет ровно один раз.

class ThreadSafeConfig {
    companion object {
        val config: String = "Config"
        
        init {
            Log.d("TAG", "Init on thread: ${Thread.currentThread().name}")
        }
    }
}

fun main() {
    // Запускаем 5 потоков которые обращаются к companion
    repeat(5) {
        thread {
            val config = ThreadSafeConfig.config
            // Все потоки увидят инициализацию только один раз
        }
    }
}

Практические примеры

1. Lazy инициализация тяжелых объектов

class ApiService {
    companion object {
        // Retrofit инициализируется только при первом обращении
        val retrofit: Retrofit by lazy {
            Retrofit.Builder()
                .baseUrl("https://api.example.com")
                .addConverterFactory(GsonConverterFactory.create())
                .build()
        }
        
        val api: UserApi = retrofit.create(UserApi::class.java)
    }
}

// Использование
fun loadUsers() {
    // Retrofit инициализируется здесь (если еще не инициализирован)
    val users = ApiService.api.getUsers()
}

2. Синглтон паттерн

class Logger {
    companion object {
        val instance = Logger()  // Создается один раз
        
        fun log(message: String) {
            println(message)
        }
    }
}

// Или более явно:
class Preferences private constructor() {
    companion object {
        @Volatile
        private var instance: Preferences? = null
        
        fun getInstance() =
            instance ?: synchronized(this) {
                instance ?: Preferences().also { instance = it }
            }
    }
}

3. Factory методы

data class User(
    val id: String,
    val name: String,
    val email: String
) {
    companion object {
        // Factory методы для создания User
        fun fromJson(json: String): User {
            // Парсим JSON
            return User("", "", "")
        }
        
        fun default(): User {
            return User("0", "Unknown", "unknown@example.com")
        }
    }
}

// Использование
val user1 = User.fromJson(jsonString)
val user2 = User.default()

Когда companion object не инициализируется

class Example {
    companion object {
        const val CONSTANT = 1  // const, подменяется компилятором
        
        init {
            println("Will not print!")
        }
    }
}

// companion object НЕ будет инициализирован
// потому что CONSTANT это const val
val value = Example.CONSTANT

// Но companion БУДЕТ инициализирован если обратимся к другому члену
val constant = Example::class.java.getDeclaredField("Companion")

Инициализация при загрузке класса

class OnLoadExample {
    companion object {
        init {
            println("Companion init")
        }
    }
    
    init {
        println("Instance init")
    }
}

fun main() {
    println("1. Before class loading")
    
    // Загружаем класс (companion еще не инициализирован)
    val clazz = OnLoadExample::class
    println("2. Class loaded")
    
    // Создаем экземпляр (companion инициализируется если еще не инициализирован)
    val instance = OnLoadExample()
    println("3. Instance created")
    
    // Обращаемся к companion через класс
    OnLoadExample::class.java.simpleName
    println("4. Class property accessed")
}

// Вывод (companion инициализируется при создании первого экземпляра):
// 1. Before class loading
// 2. Class loaded
// Companion init
// Instance init
// 3. Instance created
// 4. Class property accessed

Порядок инициализации в классе

class InitOrder {
    // Шаг 1: Инициализируются свойства класса
    val classProperty = "class"
    
    // Шаг 2: init блоки выполняются
    init {
        println("Class init 1")
    }
    
    init {
        println("Class init 2")
    }
    
    // Companion object инициализируется РАНЬШЕ первого экземпляра класса
    companion object {
        val companionProperty = "companion"
        
        init {
            println("Companion init")
        }
    }
    
    // Шаг 3: Вторичный конструктор (если есть)
    constructor(name: String) : this() {
        println("Secondary constructor")
    }
}

fun main() {
    // Companion инициализируется ДО экземпляра
    val instance = InitOrder()
}

// Вывод:
// Companion init
// Class init 1
// Class init 2

Performance соображения

// ПЛОХО — тяжелая инициализация в companion
class HeavyClass {
    companion object {
        val hugeDatabase = loadMillionRecords()  // Займет время!
        val complexCalculation = expensiveCompute()  // Займет время!
    }
}

// ХОРОШО — ленивая инициализация
class LightClass {
    companion object {
        val database: Database by lazy { loadMillionRecords() }
        val calculation: Int by lazy { expensiveCompute() }
    }
}

Итог

companion object инициализируется:

  1. При первом обращении к полю или методу (кроме const)
  2. При создании первого экземпляра класса (если companion содержит val)
  3. Потокобезопасно благодаря JVM
  4. Ровно один раз

Для оптимизации:

  • Используй const val для compile-time констант
  • Используй lazy для тяжелых инициализаций
  • Помни что companion инициализируется перед первым экземпляром
  • Избегай тяжелых инициализаций в companion если класс часто используется
Когда инициализируется companion object? | PrepBro