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.
Nội dung chính
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
và ::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!