Chào mừng độc giả Cafedev đến với thế giới phong phú của Kotlin và Reflection! Cafedev đã không ngừng là nguồn cảm hứng cho cộng đồng phần mềm và lập trình viên. Trong chuyên mục này, chúng tôi sẽ cùng nhau khám phá sức mạnh của Kotlin với Reflection – một bộ tính năng giúp nội suy cấu trúc chương trình tại thời điểm chạy. Điều này làm nổi bật tính linh hoạt và sức mạnh của ngôn ngữ lập trình này. Hãy cùng chúng tôi khám phá thêm về sự kết hợp tuyệt vời giữa Kotlin và Reflection trên Cafedev!

Reflection là một bộ tính năng ngôn ngữ và thư viện cho phép bạn nội suy cấu trúc của chương trình của bạn tại thời điểm chạy. Các hàm và thuộc tính được coi là công dân hạng nhất trong Kotlin, và khả năng nội suy chúng (ví dụ, tìm hiểu tên hoặc loại của một thuộc tính hoặc hàm tại thời điểm chạy) là quan trọng khi sử dụng phong cách hàm hoặc phản ứng.

Kotlin/JS cung cấp hỗ trợ hạn chế cho các tính năng reflection. Tìm hiểu thêm về reflection trong Kotlin/JS.

1. Phụ thuộc JVM

Trên nền tảng JVM, bản phân phối trình biên dịch Kotlin bao gồm thành phần runtime cần thiết để sử dụng các tính năng reflection dưới dạng một công cụ độc lập, kotlin-reflect.jar. Điều này được thực hiện để giảm kích thước yêu cầu của thư viện runtime đối với các ứng dụng không sử dụng tính năng reflection.
Để sử dụng reflection trong dự án Gradle hoặc Maven, thêm phụ thuộc vào kotlin-reflect:
* Trong Gradle:

Kotlin:

dependencies {
    implementation(kotlin("reflect"))
}


groovy:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.22"
}


* Trong Maven:

<dependencies>
  <dependency>
      <groupId>org.jetbrains.kotlin</groupId>
      <artifactId>kotlin-reflect</artifactId>
  </dependency>
</dependencies>

Nếu bạn không sử dụng Gradle hoặc Maven, hãy đảm bảo bạn có kotlin-reflect.jar trong classpath của dự án. Trong các trường hợp được hỗ trợ khác (dự án IntelliJ IDEA sử dụng trình biên dịch dòng lệnh hoặc Ant), nó được thêm vào mặc định. Trong trình biên dịch dòng lệnh và Ant, bạn có thể sử dụng tùy chọn biên dịch -no-reflect để loại trừ kotlin-reflect.jar khỏi classpath.

2. Tham chiếu lớp

Tính năng reflection cơ bản nhất là lấy tham chiếu thời chạy đến một lớp Kotlin. Để có tham chiếu đến một lớp Kotlin biết tĩnh, bạn có thể sử dụng cú pháp class literal:

val c = MyClass::class

Tham chiếu là một giá trị kiểu KClass.

Trên JVM: tham chiếu lớp Kotlin không giống như tham chiếu lớp Java. Để có được tham chiếu lớp Java, sử dụng thuộc tính .java trên một instance KClass.

2.1 Tham chiếu lớp ràng buộc

Bạn có thể lấy tham chiếu đến lớp của một đối tượng cụ thể bằng cách sử dụng cú pháp ::class giống như với đối tượng nhận làm tham số:

val widget: Widget = ...
assert(widget is GoodWidget) { "Bad widget: ${widget::class.qualifiedName}" }

Bạn sẽ nhận được tham chiếu đến lớp chính xác của một đối tượng, ví dụ như GoodWidget hoặc BadWidget, không phụ thuộc vào loại biểu thức nhận (Widget).

3. Tham chiếu có thể gọi được

Tham chiếu đến các hàm, thuộc tính và constructors cũng có thể được gọi hoặc sử dụng như các trường hợp của loại hàm.
Supertype chung cho tất cả các tham chiếu có thể gọi là KCallable, trong đó R là loại giá trị trả về. Đây là loại thuộc tính cho các thuộc tính và loại được tạo ra cho các constructors.

3.1 Tham chiếu hàm

Khi bạn có một hàm có tên được khai báo như dưới đây, bạn có thể gọi nó trực tiếp (isOdd(5)):

fun isOdd(x: Int) = x % 2 != 0

Hoặc bạn có thể sử dụng hàm như một giá trị loại hàm, tức là chuyển nó vào một hàm khác. Để làm điều này, sử dụng toán tử :::

val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd))

Ở đây, ::isOdd là một giá trị của loại hàm (Int) -> Boolean.
Tham chiếu hàm thuộc một trong các KFunctionloại con, phụ thuộc vào số lượng tham số. Ví dụ, KFunction3<t1, r="" t2,="" t3,=""></t1,>.
:: có thể được sử dụng với các hàm nạp chồng khi loại mong đợi được biết từ ngữ cảnh. Ví dụ:

fun isOdd(x: Int) = x % 2 != 0
fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove"

val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // refers to isOdd(x: Int)

Hoặc bạn có thể cung cấp ngữ cảnh cần thiết bằng cách lưu trữ tham chiếu phương thức trong một biến với một loại được chỉ định một cách rõ ràng:

val predicate: (String) -> Boolean = ::isOdd // refers to isOdd(x: String)

Nếu bạn cần sử dụng một thành viên của một lớp hoặc một hàm mở rộng, nó cần được xác định rõ: String::toCharArray.
Ngay cả khi bạn khởi tạo một biến với một tham chiếu đến một hàm mở rộng, loại hàm được suy luận sẽ không có bộ nhận, nhưng nó sẽ có một tham số bổ sung chấp nhận một đối tượng bộ nhận. Để có một loại hàm với bộ nhận thay vì, chỉ định loại một cách rõ ràng:

val isEmptyStringList: List.() -> Boolean = List::isEmpty

3.2 Ví dụ: hợp thành hàm

Xem xét hàm sau đây:

fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
    return { x -> f(g(x)) }
}

Nó trả về sự kết hợp của hai hàm được chuyển vào nó: compose(f, g) = f(g(*)). Bạn có thể áp dụng hàm này vào các tham chiếu có thể gọi:

fun length(s: String) = s.length

val oddLength = compose(::isOdd, ::length)
val strings = listOf("a", "ab", "abc")

println(strings.filter(oddLength))

3.3 Tham chiếu thuộc tính

Để truy cập các thuộc tính như đối tượng hạng nhất trong Kotlin, sử dụng toán tử :::

val x = 1

fun main() {
    println(::x.get())
    println(::x.name)
}

Biểu thức ::x đánh giá thành một đối tượng thuộc tính kiểu KProperty0. Bạn có thể đọc giá trị của nó bằng cách sử dụng get() hoặc lấy tên thuộc tính bằng cách sử dụng thuộc tính name. Để biết thêm thông tin, xem tài liệu về lớp <code< a=””>KProperty>.
Đối với một thuộc tính có thể thay đổi như var y = 1, ::y trả về một giá trị với kiểu KMutableProperty0 có một phương thức set():

var y = 1

fun main() {
    ::y.set(2)
    println(y)
}

Một tham chiếu thuộc tính có thể được sử dụng ở nơi một hàm với một tham số chung được mong đợi:

val strs = listOf("a", "bc", "def")
println(strs.map(String::length))

Để truy cập một thuộc tính là thành viên của một lớp, hãy xác định nó như sau:

class A(val p: Int)
val prop = A::p
println(prop.get(A(1)))

Đối với một thuộc tính mở rộng:

val String.lastChar: Char
    get() = this[length - 1]

fun main() {
    println(String::lastChar.get("abc"))
}

3.4 Tương thích với phản chiếu Java

Trên nền tảng JVM, thư viện chuẩn chứa các phần mở rộng cho các lớp phản chiếu cung cấp một ánh xạ đến và từ đối tượng phản chiếu Java (xem gói kotlin.reflect.jvm). Ví dụ, để tìm một trường hỗ trợ hoặc một phương thức Java dùng làm getter cho một thuộc tính Kotlin, bạn có thể viết như sau:

import kotlin.reflect.jvm.*

class A(val p: Int)

fun main() {
    println(A::p.javaGetter) // prints "public final int A.getP()"
    println(A::p.javaField)  // prints "private final int A.p"
}

Để lấy lớp Kotlin tương ứng với một lớp Java, sử dụng thuộc tính mở rộng .kotlin:

fun getKClass(o: Any): KClass = o.javaClass.kotlin

3.5 Tham chiếu constructor

Constructors có thể được tham chiếu giống như các phương thức và thuộc tính. Bạn có thể sử dụng chúng ở bất kỳ nơi nào chương trình mong đợi một đối tượng loại hàm nhận cùng các tham số như constructor và trả về một đối tượng của loại phù hợp. Constructors được tham chiếu bằng cách sử dụng toán tử :: và thêm tên lớp. Xem xét hàm sau đây mong đợi một tham số hàm không có tham số và kiểu trả về Foo:

class Foo

fun function(factory: () -> Foo) {
    val x: Foo = factory()
}

Sử dụng ::Foo, constructor không tham số của lớp Foo, bạn có thể gọi nó như sau:

function(::Foo)

Tham chiếu có thể gọi được đến constructors được đánh dấu là một trong các KFunction loại con phụ thuộc vào số lượng tham số.

3.6 Tham chiếu hàm và thuộc tính ràng buộc

Bạn có thể tham chiếu đến một phương thức của một đối tượng cụ thể:

val numberRegex = "\\d+".toRegex()
println(numberRegex.matches("29"))

val isNumber = numberRegex::matches
println(isNumber("29"))

Thay vì gọi phương thức matches trực tiếp, ví dụ sử dụng một tham chiếu đến nó. Một tham chiếu như vậy được ràng buộc với bộ nhận của nó. Nó có thể được gọi trực tiếp (như trong ví dụ trên) hoặc được sử dụng mỗi khi một biểu thức loại hàm được mong đợi:

val numberRegex = "\\d+".toRegex()
val strings = listOf("abc", "124", "a70")
println(strings.filter(numberRegex::matches))

So sánh loại của tham chiếu được ràng buộc và không được ràng buộc. Tham chiếu có thể gọi được đã có bộ nhận “đính kèm” với nó, nên loại của bộ nhận không còn là một tham số nữa:

val isNumber: (CharSequence) -> Boolean = numberRegex::matches

val matches: (Regex, CharSequence) -> Boolean = Regex::matches

Một tham chiếu thuộc tính cũng có thể được ràng buộc:

val prop = "abc"::length
println(prop.get())

Bạn không cần phải chỉ định this làm bộ nhận: this::foo::foo tương đương.

3.7 Tham chiếu constructor ràng buộc

Một tham chiếu có thể gọi được đến constructor của một lớp nội có thể được đạt được bằng cách cung cấp một instance của lớp ngoại:

class Outer {
    inner class Inner
}

val o = Outer()
val boundInnerCtor = o::Inner

Cảm ơn bạn đã dành thời gian khám phá Kotlin with Reflection trên Cafedev cùng chúng tôi! Chúng tôi hy vọng rằng thông tin chia sẻ đã mang đến cái nhìn sâu sắc về sức mạnh của ngôn ngữ này và khả năng nâng cao trải nghiệm lập trình. Đừng quên theo dõi các bài viết tiếp theo trên Cafedev để không bỏ lỡ những kiến thức mới và cập nhật về Kotlin và các xu hướng phát triển công nghệ. Cùng Cafedev, chúng ta sẽ tiếp tục hành trình khám phá và chia sẻ kiến thức lập trình đầy hứng khởi!”

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!