👥 Companion Object
🎯 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
Phần tiêu đề “💡 Vấn đề: Kotlin không có static”Java sử dụng static cho class-level members:
// Javapublic class User { public static final int MAX_NAME_LENGTH = 50;
public static User fromJson(String json) { return new User(json); }}
// Sử dụngUser.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ư staticUser.MAX_NAME_LENGTHUser.fromJson("{...}")📝 Cú pháp Companion Object
Phần tiêu đề “📝 Cú pháp Companion Object”Cơ bản
Phần tiêu đề “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
Phần tiêu đề “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")}🏭 Factory Pattern
Phần tiêu đề “🏭 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
Phần tiêu đề “📌 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}🔌 Implement Interface
Phần tiêu đề “🔌 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", "parsed@example.com") }
override fun schema(): String = """ { "name": "string", "email": "string" } """.trimIndent() }}
// Generic function working with any JsonFactoryfun <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
Phần tiêu đề “🔗 Extension Functions cho Companion Object”class User(val name: String) { companion object // Empty companion object}
// Extension function cho companion objectfun 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
Phần tiêu đề “☕ Java Interoperability”Gọi từ Java
Phần tiêu đề “Gọi từ Java”// Kotlinclass 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ụngpublic 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
Phần tiêu đề “🛠️ 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 methodsLờ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
Phần tiêu đề “📱 Trong Android”// Fragment/Activity argumentsclass 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 extrasclass 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) } } }}
// ViewModelFactoryclass 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
Phần tiêu đề “⚠️ Lưu ý quan trọng”✅ Checklist - Tự kiểm tra
Phần tiêu đề “✅ 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 valcho compile-time constants - Implement interface trong companion object
- Sử dụng
@JvmStaticvà@JvmFieldcho Java interop - Tạo extension functions cho companion object
Tiếp theo: Object Declaration