Skip to Content
Kotlin📘 Ngôn ngữ Kotlin👥 Companion Object

Companion Object trong Kotlin

🎯 Mục tiêu: Hiểu Companion Object - cách Kotlin thay thế static members của Java, factory patterns, và tương tác với Java code.


💡 Vấn đề: Kotlin không có static

Java sử dụng static cho class-level members:

// Java public class User { public static final int MAX_NAME_LENGTH = 50; public static User fromJson(String json) { return new User(json); } } // Sử dụng User.MAX_NAME_LENGTH; User.fromJson("{...}");

Kotlin không có static keyword. Thay vào đó, sử dụng companion object:

class User(val name: String) { companion object { const val MAX_NAME_LENGTH = 50 fun fromJson(json: String): User { return User(json) // Simplified } } } // Sử dụng giống như static User.MAX_NAME_LENGTH User.fromJson("{...}")

📝 Cú pháp Companion Object

Cơ bản

class MyClass { companion object { val property = "value" fun method() = println("Hello from companion") } } fun main() { println(MyClass.property) // value MyClass.method() // Hello from companion }

Named Companion Object

class User(val name: String) { companion object Factory { // Đặt tên là "Factory" fun create(name: String) = User(name) fun createGuest() = User("Guest") } } fun main() { // Cả hai cách đều hoạt động val user1 = User.create("Alice") val user2 = User.Factory.create("Bob") }
ℹ️

Nếu không đặt tên, companion object có tên mặc định là Companion:

class User { companion object { } } User.Companion // Access tên mặc định

🏭 Factory Pattern

Companion object rất phù hợp cho Factory pattern:

class DatabaseConnection private constructor( val host: String, val port: Int, val database: String ) { companion object { private var instance: DatabaseConnection? = null // Factory methods fun production(): DatabaseConnection { return instance ?: DatabaseConnection( host = "prod.db.example.com", port = 5432, database = "production" ).also { instance = it } } fun development(): DatabaseConnection { return DatabaseConnection( host = "localhost", port = 5432, database = "dev" ) } fun fromConfig(config: Map<String, String>): DatabaseConnection { return DatabaseConnection( host = config["host"] ?: "localhost", port = config["port"]?.toIntOrNull() ?: 5432, database = config["database"] ?: "default" ) } } override fun toString() = "Connection($host:$port/$database)" } fun main() { val prod = DatabaseConnection.production() val dev = DatabaseConnection.development() println(prod) // Connection(prod.db.example.com:5432/production) println(dev) // Connection(localhost:5432/dev) }

📌 Constants với const val

class AppConfig { companion object { // Compile-time constants const val VERSION = "1.0.0" const val API_VERSION = 2 const val BASE_URL = "https://api.example.com" // Runtime values (không phải const) val startTime = System.currentTimeMillis() // ❌ Compile error - const chỉ cho primitives và String // const val list = listOf(1, 2, 3) } } fun main() { println(AppConfig.VERSION) // 1.0.0 println(AppConfig.API_VERSION) // 2 println(AppConfig.startTime) // Timestamp }
⚠️

const val requirements:

  • Top-level hoặc trong object/companion object
  • Chỉ cho primitive types và String
  • Khởi tạo với compile-time constant expression

🔌 Implement Interface

Companion object có thể implement interfaces:

interface JsonFactory<T> { fun fromJson(json: String): T fun schema(): String } class User(val name: String, val email: String) { companion object : JsonFactory<User> { override fun fromJson(json: String): User { // Parse JSON (simplified) return User("parsed", "[email protected]") } override fun schema(): String = """ { "name": "string", "email": "string" } """.trimIndent() } } // Generic function working with any JsonFactory fun <T> loadFromApi(factory: JsonFactory<T>, json: String): T { return factory.fromJson(json) } fun main() { val user = loadFromApi(User, """{"name": "Alice"}""") println(User.schema()) }

🔗 Extension Functions cho Companion Object

class User(val name: String) { companion object // Empty companion object } // Extension function cho companion object fun User.Companion.createAdmin(name: String): User { return User("Admin: $name") } fun User.Companion.createFromEnvironment(): User { val name = System.getenv("USER") ?: "Unknown" return User(name) } fun main() { val admin = User.createAdmin("Alice") val envUser = User.createFromEnvironment() println(admin.name) // Admin: Alice println(envUser.name) // Current system user }

☕ Java Interoperability

Gọi từ Java

// Kotlin class User(val name: String) { companion object { const val MAX_LENGTH = 50 @JvmStatic // Tạo static method thực sự fun create(name: String) = User(name) @JvmField // Expose field thay vì getter val DEFAULT = User("Default") } }
// Java - sử dụng public class Main { public static void main(String[] args) { // const val - tự động static int max = User.MAX_LENGTH; // @JvmStatic User user = User.create("Alice"); // @JvmField User defaultUser = User.DEFAULT; // Không có annotation - phải dùng Companion // User.Companion.someMethod(); } }

🛠️ Thực hành

Bài tập: Tạo Logger singleton với companion object

// TODO: Tạo Logger class với: // - Private constructor // - Companion object với instance getter // - log(level: LogLevel, message: String) // - info(), warn(), error() convenience methods

Lời giải:

enum class LogLevel { INFO, WARN, ERROR } class Logger private constructor(private val tag: String) { companion object { private val instances = mutableMapOf<String, Logger>() fun getInstance(tag: String = "APP"): Logger { return instances.getOrPut(tag) { Logger(tag) } } // Convenience methods fun info(message: String) = getInstance().info(message) fun warn(message: String) = getInstance().warn(message) fun error(message: String) = getInstance().error(message) } fun log(level: LogLevel, message: String) { val timestamp = java.time.LocalDateTime.now() println("[$timestamp][$tag][$level] $message") } fun info(message: String) = log(LogLevel.INFO, message) fun warn(message: String) = log(LogLevel.WARN, message) fun error(message: String) = log(LogLevel.ERROR, message) } fun main() { // Using default logger Logger.info("Application started") Logger.warn("Low memory") // Using tagged logger val dbLogger = Logger.getInstance("DATABASE") dbLogger.info("Connected to database") dbLogger.error("Query failed") }

📱 Trong Android

// Fragment/Activity arguments class UserFragment : Fragment() { companion object { private const val ARG_USER_ID = "user_id" private const val ARG_USER_NAME = "user_name" fun newInstance(userId: Long, userName: String): UserFragment { return UserFragment().apply { arguments = Bundle().apply { putLong(ARG_USER_ID, userId) putString(ARG_USER_NAME, userName) } } } } private val userId by lazy { arguments?.getLong(ARG_USER_ID) ?: -1 } private val userName by lazy { arguments?.getString(ARG_USER_NAME) ?: "" } } // Intent extras class DetailActivity : AppCompatActivity() { companion object { const val EXTRA_ITEM_ID = "extra_item_id" fun createIntent(context: Context, itemId: Long): Intent { return Intent(context, DetailActivity::class.java).apply { putExtra(EXTRA_ITEM_ID, itemId) } } } } // ViewModelFactory class UserViewModel(private val repository: UserRepository) : ViewModel() { companion object { fun factory(repository: UserRepository) = object : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { @Suppress("UNCHECKED_CAST") return UserViewModel(repository) as T } } } }

⚠️ Lưu ý quan trọng

💡

Companion Object vs Object Declaration:

// Companion object - gắn với class class User { companion object { } // Chỉ 1 companion per class } // Object declaration - standalone singleton object Database { } // Singleton riêng
⚠️

Companion object được khởi tạo lazy:
Nó chỉ được khởi tạo khi lần đầu tiên accessed, không phải khi class được load.


✅ Checklist - Tự kiểm tra

Sau bài học này, bạn có thể:

  • Hiểu tại sao Kotlin dùng companion object thay vì static
  • Tạo companion object với properties và methods
  • Sử dụng named companion object
  • Tạo factory methods trong companion object
  • Sử dụng const val cho compile-time constants
  • Implement interface trong companion object
  • Sử dụng @JvmStatic@JvmField cho Java interop
  • Tạo extension functions cho companion object

Tiếp theo: Object Declaration

Last updated on