Bỏ qua để đến nội dung

🏷️ Annotations

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.

@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
}
// @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 ExperimentalFeature
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
value class Password(val value: String)

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() {}
}
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
annotation class MyAnnotation
@MyAnnotation
class MyClass
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 Tutorial
// @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)
@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).

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() { }
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"}
}
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)
}
// ✅ 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
)

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 RUNTIME retention khi cần đọc via reflection
  • Dùng SOURCE cho tool-only annotations
  • Always specify @Target để prevent misuse