Annotations trong Kotlin
1. Giới thiệu
Annotations là metadata được gắn vào code. Chúng không thay đổi behavior trực tiếp nhưng có thể được đọc bởi compiler, tools, hoặc runtime để xử lý đặc biệt.
2. Sử dụng Annotations
@Deprecated("Use newFunction() instead", ReplaceWith("newFunction()"))
fun oldFunction() {
println("Old implementation")
}
fun newFunction() {
println("New implementation")
}
@Suppress("UNCHECKED_CAST")
fun unsafeCast(obj: Any): String {
return obj as String
}
fun main() {
oldFunction() // Warning: deprecated
}3. Built-in Annotations
Kotlin Annotations
// @Deprecated - đánh dấu deprecated
@Deprecated(
message = "Use newApi() instead",
replaceWith = ReplaceWith("newApi()"),
level = DeprecationLevel.WARNING // WARNING, ERROR, HIDDEN
)
fun oldApi() {}
// @Suppress - suppress warnings
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
fun example(unused: String) {
val x = 5
}
// @OptIn - opt-in to experimental APIs
@OptIn(ExperimentalStdlibApi::class)
fun useExperimental() {
// Use experimental APIs
}
// @DslMarker - for DSL type safety
@DslMarker
annotation class HtmlDsl
// @RequiresOptIn - mark API as experimental
@RequiresOptIn(message = "This API is experimental")
annotation class ExperimentalFeatureJVM Interop Annotations
class MyClass {
companion object {
// @JvmStatic - generate static method for Java
@JvmStatic
fun staticMethod() {
println("Can be called as MyClass.staticMethod() in Java")
}
// @JvmField - expose as public field
@JvmField
val CONSTANT = "VALUE"
}
// @JvmOverloads - generate overloaded methods
@JvmOverloads
fun greet(name: String = "World", greeting: String = "Hello") {
println("$greeting, $name!")
}
// @JvmName - custom JVM name
@get:JvmName("getFullNameProperty")
val fullName: String = "John Doe"
// @Throws - declare exceptions for Java
@Throws(IOException::class)
fun readFile(path: String): String {
return java.io.File(path).readText()
}
}@JvmInline cho Value Classes
@JvmInline
value class Password(val value: String)4. Annotation Targets
Chỉ định vị trí áp dụng annotation:
// file target
@file:JvmName("Utils")
package com.example
// property target
class Example {
@property:Deprecated("Old property")
val oldProp: String = ""
// field target (backing field)
@field:Volatile
var counter: Int = 0
// getter target
@get:JvmName("fetchValue")
val value: Int = 0
// setter target
var name: String = ""
@set:Synchronized
set
// param target
fun process(@param:NotNull input: String) {}
// receiver target
fun @receiver:NotNull String.extend() {}
}Use-site targets
| Target | Áp dụng cho |
|---|---|
file: | Entire file |
property: | Property (Kotlin) |
field: | Backing field |
get: | Property getter |
set: | Property setter |
param: | Constructor/function param |
receiver: | Extension receiver |
delegate: | Delegate field |
5. Defining Custom Annotations
Basic annotation
annotation class MyAnnotation
@MyAnnotation
class MyClassAnnotation với properties
annotation class Info(
val author: String,
val version: Int,
val description: String = "" // default value
)
@Info(author = "Alice", version = 1, description = "Example class")
class Example
// Array values
annotation class Tags(val values: Array<String>)
@Tags(["kotlin", "android", "tutorial"])
class TutorialMeta-annotations
// @Target - where annotation can be applied
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY
)
annotation class Auditable
// @Retention - when annotation is available
@Retention(AnnotationRetention.RUNTIME) // Available at runtime
annotation class RuntimeAnnotation
@Retention(AnnotationRetention.BINARY) // In bytecode, not at runtime
annotation class BinaryAnnotation
@Retention(AnnotationRetention.SOURCE) // Only in source code
annotation class SourceAnnotation
// @Repeatable - can be applied multiple times
@Repeatable
annotation class Author(val name: String)
@Author("Alice")
@Author("Bob")
class Book
// @MustBeDocumented - include in documentation
@MustBeDocumented
annotation class Documented(val description: String)6. Annotation Processing (Compile-time)
Ví dụ: Custom lint annotation
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class MainThread
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class WorkerThread
class NetworkService {
@WorkerThread
fun fetchData(): String {
// Must be called on background thread
return "data"
}
@MainThread
fun updateUi(data: String) {
// Must be called on main thread
}
}Note: Annotation processing cần thêm annotation processor (như KAPT hoặc KSP).
7. Annotations trong Android
class MainActivity : AppCompatActivity() {
// View binding/injection
// @BindView(R.id.textView) lateinit var textView: TextView // ButterKnife
// Dagger/Hilt
// @Inject lateinit var repository: UserRepository
// Room Database
// @Entity(tableName = "users")
// data class User(@PrimaryKey val id: Int, val name: String)
// Serialization
// @Serializable data class ApiResponse(val data: String)
}
// Compose
// @Composable fun MyScreen() { }
// @Preview @Composable fun PreviewMyScreen() { }8. Kotlinx Serialization Annotations
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class User(
@SerialName("user_id")
val id: Int,
val name: String,
@Transient // Excluded from serialization
val password: String = "",
@EncodeDefault
val role: String = "user"
)
fun main() {
val user = User(1, "Alice", "secret123")
val json = Json.encodeToString(user)
println(json) // {"user_id":1,"name":"Alice","role":"user"}
}9. Reading Annotations at Runtime
import kotlin.reflect.full.*
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Entity(val tableName: String)
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class Column(val name: String, val primaryKey: Boolean = false)
@Entity("users")
data class User(
@Column("id", primaryKey = true)
val id: Int,
@Column("user_name")
val name: String,
@Column("email_address")
val email: String
)
fun generateCreateTable(clazz: kotlin.reflect.KClass<*>): String {
val entity = clazz.findAnnotation<Entity>()
?: throw IllegalArgumentException("Missing @Entity annotation")
val columns = clazz.memberProperties
.mapNotNull { prop ->
prop.findAnnotation<Column>()?.let { col ->
"${col.name} TEXT${if (col.primaryKey) " PRIMARY KEY" else ""}"
}
}
.joinToString(", ")
return "CREATE TABLE ${entity.tableName} ($columns)"
}
fun main() {
val sql = generateCreateTable(User::class)
println(sql)
// CREATE TABLE users (id TEXT PRIMARY KEY, user_name TEXT, email_address TEXT)
}10. Best Practices
// ✅ Dùng Target để restrict usage
@Target(AnnotationTarget.FUNCTION)
annotation class NeedsPermission(val permission: String)
// ✅ Dùng Retention phù hợp
@Retention(AnnotationRetention.RUNTIME) // Cần đọc at runtime
annotation class Injectable
@Retention(AnnotationRetention.SOURCE) // Chỉ cho compiler/tools
annotation class SuppressLint(val value: String)
// ✅ Document annotations
@MustBeDocumented
@Target(AnnotationTarget.FUNCTION)
annotation class Authenticated(
val roles: Array<String> = []
)
// ✅ Combine related annotations
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class SecureEndpoint(
val authenticated: Boolean = true,
val roles: Array<String> = [],
val rateLimit: Int = 100
)📝 Tóm tắt
| Meta-annotation | Purpose |
|---|---|
@Target | Nơi annotation có thể được apply |
@Retention | Khi annotation available (SOURCE/BINARY/RUNTIME) |
@Repeatable | Cho phép apply nhiều lần |
@MustBeDocumented | Include trong documentation |
Common JVM annotations:
@JvmStatic- Static methods cho Java@JvmField- Public fields cho Java@JvmOverloads- Generate overloaded methods@JvmName- Custom JVM name@Throws- Declare exceptions
Best practices:
- Dùng
RUNTIMEretention khi cần đọc via reflection - Dùng
SOURCEcho tool-only annotations - Always specify
@Targetđể prevent misuse
Last updated on