Skip to Content

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 ExperimentalFeature

JVM 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 MyClass

Annotation 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 Tutorial

Meta-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-annotationPurpose
@TargetNơi annotation có thể được apply
@RetentionKhi annotation available (SOURCE/BINARY/RUNTIME)
@RepeatableCho phép apply nhiều lần
@MustBeDocumentedInclude 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
Last updated on