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 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
// 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 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
Last updated on