Chào độc giả của Cafedev! Hôm nay, chúng ta sẽ khám phá thế giới của Kotlin với chú thích (Annotations). Đây là một phần quan trọng trong ngôn ngữ lập trình này, giúp bạn đính kèm metadata vào mã nguồn. Cùng Cafedev đàm phán về cách sử dụng, cú pháp, và những điều quan trọng khi làm việc với chú thích trong Kotlin. Hãy bắt đầu hành trình khám phá mới của bạn với Kotlin và sức mạnh của chú thích!

Annotations là cách đính kèm metadata vào mã nguồn. Để khai báo một annotation, đặt từ khóa annotation trước một lớp:

annotation class Fancy

Các thuộc tính bổ sung của annotation có thể được xác định bằng cách đánh dấu lớp annotation với meta-annotations:

  • @Target chỉ định loại phần tử có thể được đánh dấu bằng annotation (như lớp, hàm, thuộc tính và biểu thức);
  • @Retention xác định liệu annotation có được lưu trữ trong các tệp lớp đã biên dịch và có thể nhìn thấy thông qua reflection khi chạy (mặc định, cả hai đều đúng);
  • @Repeatable cho phép sử dụng cùng một annotation trên một phần tử nhiều lần;
  • @MustBeDocumented xác định rằng annotation là một phần của API công khai và nên được bao gồm trong chữ ký lớp hoặc phương thức được hiển thị trong tài liệu API tạo ra.
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
        AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.VALUE_PARAMETER,
        AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy

1. Sử dụng

@Fancy class Foo {
    @Fancy fun baz(@Fancy foo: Int): Int {
        return (@Fancy 1)
    }
}

Nếu bạn cần đánh dấu constructor chính của một lớp, bạn cần thêm từ khóa constructor vào khai báo constructor và thêm các annotations trước nó:

class Foo @Inject constructor(dependency: MyDependency) { ... }

Bạn cũng có thể đánh dấu truy cập thuộc tính:

class Foo {
    var x: MyDependency? = null
        @Inject set
}

2. Constructors

Annotations có thể có constructors nhận tham số.

annotation class Special(val why: String)

@Special("example") class Foo {}

Các loại tham số được phép là:

  • Các loại tương ứng với các loại nguyên thủy của Java (Int, Long v.v.)
  • String
  • Các lớp (Foo::class)
  • Enums
  • Annotations
  • Mảng của các loại đã liệt kê ở trên


Tham số của annotation không thể có loại có thể null, vì JVM không hỗ trợ lưu trữ null như một giá trị của thuộc tính annotation.
Nếu một annotation được sử dụng làm tham số của một annotation khác, tên của nó không được tiếp đầu ngữ bằng ký tự @:

annotation class ReplaceWith(val expression: String)

annotation class Deprecated(
        val message: String,
        val replaceWith: ReplaceWith = ReplaceWith(""))

@Deprecated("This function is deprecated, use === instead", ReplaceWith("this === other"))


Nếu bạn cần chỉ định một lớp làm đối số của một annotation, sử dụng một lớp Kotlin(KClass). Trình biên dịch Kotlin sẽ tự động chuyển đổi nó thành một lớp Java, để mã Java có thể truy cập các annotations và đối số một cách bình thường.

import kotlin.reflect.KClass

annotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any>)

@Ann(String::class, Int::class) class MyClass

3. Khởi tạo

Trong Java, một loại chú thích là một dạng của một interface, vì vậy bạn có thể triển khai nó và sử dụng một instance. Như một lựa chọn cho cơ chế này, Kotlin cho phép bạn gọi một constructor của một lớp chú thích trong mã nguồn tùy ý và tương tự sử dụng instance kết quả.

annotation class InfoMarker(val info: String)

fun processInfo(marker: InfoMarker): Unit = TODO()

fun main(args: Array<String>) {
    if (args.isNotEmpty())
        processInfo(getAnnotationReflective(args))
    else
        processInfo(InfoMarker("default"))
}

Tìm hiểu thêm về việc khởi tạo các lớp chú thích tại đây.

4. Hàm lambda

Chú thích cũng có thể được sử dụng trên các hàm lambda. Chúng sẽ được áp dụng cho phương thức invoke() mà vào đó cơ thể của lambda được tạo ra. Điều này hữu ích cho các framework như Quasar, sử dụng chú thích để kiểm soát đồng thời.

annotation class Suspendable

val f = @Suspendable { Fiber.sleep(10) }

5. Mục tiêu sử dụng chú thích

Khi bạn đang chú thích một thuộc tính hoặc tham số constructor chính, có nhiều phần tử Java được tạo ra từ phần tử Kotlin tương ứng, và do đó có nhiều vị trí có thể cho chú thích trong bytecode Java được tạo ra. Để chỉ định chính xác làm thế nào chú thích nên được tạo ra, hãy sử dụng cú pháp sau:

class Example(@field:Ann val foo,    // annotate Java field
              @get:Ann val bar,      // annotate Java getter
              @param:Ann val quux)   // annotate Java constructor parameter


Cùng một cú pháp có thể được sử dụng để chú thích toàn bộ tệp. Để làm điều này, đặt một chú thích với mục tiêu là file ở cấp độ trên cùng của một tệp, trước chỉ thị gói hoặc trước tất cả các imports nếu tệp ở gói mặc định:

@file:JvmName("Foo")

package org.jetbrains.demo

Nếu bạn có nhiều chú thích với cùng một mục tiêu, bạn có thể tránh lặp lại mục tiêu bằng cách thêm dấu ngoặc vuông sau mục tiêu và đặt tất cả các chú thích bên trong nó:

class Example {
     @set:[Inject VisibleForTesting]
     var collaborator: Collaborator
}

Danh sách đầy đủ các mục tiêu sử dụng chú thích hỗ trợ:

  • file property (chú thích với mục tiêu này không hiển thị cho Java)
  • field
  • get (phương thức getter của thuộc tính)
  • set (phương thức setter của thuộc tính)
  • receiver (tham số receiver của một hàm mở rộng hoặc thuộc tính)
  • param (tham số constructor)
  • setparam (tham số của phương thức setter của thuộc tính)
  • delegate (trường lưu trữ thể hiện đối tượng đại diện cho thuộc tính được ủy quyền)

Để chú thích tham số receiver của một hàm mở rộng, sử dụng cú pháp sau:

fun @receiver:Fancy String.myExtension() { ... }


Nếu bạn không chỉ định một mục tiêu sử dụng chú thích, mục tiêu sẽ được chọn dựa trên chú thích @Target của chú thích đang được sử dụng. Nếu có nhiều mục tiêu áp dụng, mục tiêu áp dụng đầu tiên từ danh sách sau sẽ được sử dụng:

  • param
  • property
  • field

6. Chú thích trong Java

Chú thích trong Java hoàn toàn tương thích với Kotlin:

import org.junit.Test
import org.junit.Assert.*
import org.junit.Rule
import org.junit.rules.*

class Tests {
    // apply @Rule annotation to property getter
    @get:Rule val tempFolder = TemporaryFolder()

    @Test fun simple() {
        val f = tempFolder.newFile()
        assertEquals(42, getTheAnswer())
    }
}


Vì thứ tự của các tham số cho một chú thích được viết bằng Java không được xác định, bạn không thể sử dụng cú pháp gọi hàm thông thường để truyền các đối số. Thay vào đó, bạn cần sử dụng cú pháp đặt tên cho đối số:

// Java
public @interface Ann {
    int intValue();
    String stringValue();
}
// Kotlin
@Ann(intValue = 1, stringValue = "abc") class C


Giống như ở Java, trường hợp đặc biệt là tham số value; giá trị của nó có thể được chỉ định mà không cần tên rõ ràng:

// Java
public @interface AnnWithValue {
    String value();
}

6.1 Mảng làm tham số chú thích

Nếu đối số value trong Java có kiểu mảng, nó trở thành một tham số vararg trong Kotlin:

// Java
public @interface AnnWithArrayValue {
    String[] value();
}

Đối với các đối số khác có kiểu mảng, bạn cần sử dụng cú pháp mảng hoặc arrayOf(...):

// Java
public @interface AnnWithArrayValue {
    String[] value();
}
// Kotlin
@AnnWithArrayValue("abc", "foo", "bar") class C

6.2 Truy cập các thuộc tính của một thể hiện chú thích(annotation)

Giá trị của một thể hiện chú thích được tiết lộ như là các thuộc tính cho mã nguồn Kotlin:

// Java
public @interface Ann {
    int value();
}
// Kotlin
fun foo(ann: Ann) {
    val i = ann.value
}

6.3 Khả năng không tạo mục tiêu chú thích JVM 1.8+

Nếu một chú thích Kotlin có TYPE trong các mục tiêu Kotlin của nó, chú thích sẽ ánh xạ sang java.lang.annotation.ElementType.TYPE_USE trong danh sách các mục tiêu chú thích Java của nó. Điều này giống như cách mục tiêu Kotlin TYPE_PARAMETER ánh xạ sang mục tiêu Java java.lang.annotation.ElementType.TYPE_PARAMETER. Điều này là một vấn đề đối với các ứng dụng Android với API level dưới 26, không có các mục tiêu này trong API.
Để tránh tạo ra các mục tiêu chú thích TYPE_USETYPE_PARAMETER, sử dụng đối số trình biên dịch mới -Xno-new-java-annotation-targets.

7. Chú thích lặp lại

Tương tự như trong Java, Kotlin có chú thích lặp lại, có thể được áp dụng cho một phần mã lệnh một số lần. Để chú thích của bạn có thể lặp lại, hãy đánh dấu phần khai báo bằng @kotlin.annotation.Repeatable meta-annotation. Điều này sẽ khiến nó có thể lặp lại cả trong Kotlin và Java. Chú thích lặp lại trong Java cũng được hỗ trợ từ phía Kotlin.
Sự khác biệt chính so với hệ thống sử dụng trong Java là sự vắng mặt của một containing annotation, mà trình biên dịch Kotlin tạo tự động với một tên được định nghĩa trước. Đối với một chú thích trong ví dụ dưới đây, nó sẽ tạo ra chú thích chứa là @Tag.Container:

@Repeatable
annotation class Tag(val name: String)

// The compiler generates the @Tag.Container containing annotation


Bạn có thể đặt tên tùy chỉnh cho một chú thích chứa bằng cách áp dụng @kotlin.jvm.JvmRepeatable meta-annotation và truyền một lớp chú thích chứa được chỉ định một cách rõ ràng như đối số:

@JvmRepeatable(Tags::class)
annotation class Tag(val name: String)

annotation class Tags(val value: Array<Tag>)


Để trích xuất chú thích lặp lại trong Kotlin hoặc Java thông qua reflection, sử dụng hàm KAnnotatedElement.findAnnotations().

Cảm ơn bạn đã đồng hành cùng Cafedev trong chuyến hành trình khám phá Kotlin với chú thích. Hy vọng bạn đã tìm thấy những thông tin hữu ích và động lực mới. Nếu có bất kỳ thắc mắc hay ý kiến đóng góp, hãy chia sẻ ngay trên Cafedev để chúng ta cùng nhau tạo nên một cộng đồng vững mạnh. Cảm ơn bạn đã theo dõi và đừng quên quay trở lại để khám phá thêm nhiều điều thú vị khác về thế giới của Kotlin!

Các nguồn kiến thức MIỄN PHÍ VÔ GIÁ từ cafedev tại đây

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của Cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!