Chào mừng các bạn đến với Cafedev, nơi chia sẻ và kết nối cộng đồng lập trình viên Việt Nam! Hôm nay, chúng ta sẽ cùng nhau khám phá một khía cạnh mới của Kotlin – thế giới của Inline Functions. Điều đặc biệt là chúng ta sẽ tìm hiểu cách sử dụng chúng để tối ưu hóa mã nguồn và tăng hiệu suất trong lập trình. Đừng bỏ lỡ cơ hội đắng này, hãy cùng nhau đàm đạo và tìm hiểu về những điều mới mẻ trong Kotlin!
Sử dụng các hàm bậc cao đặt một số chi phí thời gian chạy cụ thể: mỗi hàm là một đối tượng và nó ghi lại một closure. Một closure là một phạm vi của biến có thể được truy cập trong thân hàm. Cả việc cấp phát bộ nhớ (cả cho đối tượng hàm và các lớp) và cuộc gọi ảo đều mang lại chi phí thời gian chạy.
Nhưng dường như trong nhiều trường hợp, loại chi phí này có thể được loại bỏ bằng cách nội suy các biểu thức lambda. Các hàm được hiển thị dưới đây là ví dụ tốt về tình huống này. Hàm lock()
có thể dễ dàng được nội suy tại các điểm gọi. Xem xét trường hợp sau đây:
lock(l) { foo() }
Thay vì tạo một đối tượng hàm cho tham số và tạo một cuộc gọi, trình biên dịch có thể phát ra mã nguồn sau đây:
l.lock()
try {
foo()
} finally {
l.unlock()
}
Để làm cho trình biên dịch thực hiện điều này, đánh dấu hàm lock()
với từ khóa inline
:
inline fun lock(lock: Lock, body: () -> T): T { ... }
Từ khóa inline
ảnh hưởng cả đến hàm chính và các lambda được truyền vào nó: tất cả chúng sẽ được nội suy vào điểm gọi.
Nội suy có thể làm cho mã nguồn được tạo ra tăng lên. Tuy nhiên, nếu bạn thực hiện điều này một cách hợp lý (tránh nội suy các hàm lớn), nó sẽ đền đáp trong hiệu suất, đặc biệt là tại các điểm gọi “đa hình” bên trong vòng lặp.
Nội dung chính
1.noinline
Nếu bạn không muốn tất cả các lambda được truyền vào một hàm nội suy, đánh dấu một số tham số hàm của bạn bằng từ khóa noinline
:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { ... }
Lambda có thể được nội suy chỉ bên trong các hàm nội suy hoặc được truyền như các đối số có thể được nội suy. Lambda có noinline
, tuy nhiên, có thể được điều chỉnh bằng bất kỳ cách nào bạn muốn, bao gồm việc lưu trữ trong các trường hoặc truyền đi.
Nếu một hàm nội suy không có tham số hàm có thể được nội suy và không có tham số kiểu đã được reified, trình biên dịch sẽ phát ra một cảnh báo, vì việc nội suy những hàm như vậy rất ít khi mang lại lợi ích (bạn có thể sử dụng chú thích @Suppress("NOTHING_TO_INLINE")
để tắt cảnh báo nếu bạn chắc chắn rằng việc nội suy là cần thiết).
2. Trả về không thuộc local
Trong Kotlin, bạn chỉ có thể sử dụng return
thông thường, không được quyền lực để thoát khỏi một hàm có tên hoặc một hàm vô danh. Để thoát khỏi một lambda, sử dụng một nhãn. Một return
trần không được phép bên trong một lambda vì một lambda không thể làm cho hàm bao quanh trở về:
fun foo() {
ordinaryFunction {
return // ERROR: cannot make `foo` return here
}
}
Nhưng nếu hàm mà lambda được truyền vào được nội suy, thì return
cũng có thể được nội suy. Vì vậy nó là được phép:
fun foo() {
inlined {
return // OK: the lambda is inlined
}
}
Những trả về như vậy (nằm trong một lambda, nhưng thoát khỏi hàm bao quanh) được gọi là trả về không thuộc địa phương. Loại xây dựng này thường xuyên xuất hiện trong các vòng lặp, mà các hàm nội suy thường bao quanh:
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // returns from hasZeros
}
return false
}
Lưu ý rằng một số hàm nội suy có thể gọi các lambda được truyền vào chúng dưới dạng tham số không trực tiếp từ thân hàm, mà từ một ngữ cảnh thực thi khác, như một đối tượng cục bộ hoặc một hàm lồng. Trong trường hợp như vậy, luồng điều khiển không thuộc địa phương cũng không được phép trong các lambda. Để chỉ định rằng tham số lambda của hàm nội suy không thể sử dụng trả về không thuộc địa phương,đánh dấu tham số lambda với từ khóa crossinline
:
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ...
}
break
và continue
hiện chưa có sẵn trong các lambda được nội suy, nhưng chúng tôi đang có kế hoạch hỗ trợ chúng.
3. Tham số kiểu đã được reified
Đôi khi bạn cần truy cập một kiểu được chuyển làm tham số:
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
var p = parent
while (p != null && !clazz.isInstance(p)) {
p = p.parent
}
@Suppress("UNCHECKED_CAST")
return p as T?
}
Ở đây, bạn đi lên một cây và sử dụng phản ánh để kiểm tra xem một nút có một kiểu cụ thể hay không. Mọi thứ đều ổn, nhưng trang gọi không đẹp lắm:
treeNode.findParentOfType(MyTreeNode::class.java)
Một giải pháp tốt hơn là đơn giản là truyền một kiểu vào hàm này. Bạn có thể gọi nó như sau:
treeNode.findParentOfType()
Để làm cho điều này có thể, các hàm nội suy hỗ trợ tham số kiểu đã được reified, vì vậy bạn có thể viết một cái gì đó như thế này:
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}
Mã trên đặt biệt tham số kiểu với từ khóa reified
để làm cho nó có thể truy cập bên trong hàm, gần như như là một lớp bình thường. Vì hàm được nội suy, không cần phải sử dụng phản ánh và các toán tử bình thường như !is
và as
giờ đây đã có sẵn để bạn sử dụng. Ngoài ra, bạn có thể gọi hàm như đã hiển thị ở trên: myTree.findParentOfType()
.
Mặc dù phản ánh có thể không cần thiết trong nhiều trường hợp, bạn vẫn có thể sử dụng nó với một tham số kiểu đã được reified:
inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>) {
println(membersOf<StringBuilder>().joinToString("\n"))
}
Hàm bình thường (không được đánh dấu là nội suy) không thể có tham số kiểu đã được reified. Một kiểu không có biểu diễn thời gian chạy (ví dụ, một tham số kiểu không được reified hoặc một kiểu giả tưởng như Nothing
) không thể được sử dụng như một đối số cho một tham số kiểu đã được reified.
4. Thuộc tính nội suy
Từ khóa inline
có thể được sử dụng trên các truy cập của các thuộc tính không có trường hậu cần. Bạn có thể chú thích từng truy cập thuộc tính riêng:
Trường hậu cần là: một trường chỉ được dùng như một phần của thuộc tính để lưu giữ giá trị của nó trong bộ nhớ. Các trường không thể được khai báo trực tiếp. Tuy nhiên, khi một thuộc tính cần trường sao lưu, Kotlin sẽ tự động cung cấp trường đó. Trường sao lưu này có thể được tham chiếu trong các trình truy cập bằng cách sử dụng mã định danh trường.
Trường hậu cần sẽ được tạo cho một thuộc tính nếu nó sử dụng cách triển khai mặc định của ít nhất một trong các trình truy cập hoặc nếu một trình truy cập tùy chỉnh tham chiếu nó thông qua mã định danh trường
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ...
inline set(v) { ... }
Bạn cũng có thể chú thích toàn bộ thuộc tính, điều này đánh dấu cả hai truy cập của nó là inline
:
inline var bar: Bar
get() = ...
set(v) { ... }
Tại điểm gọi, các truy cập thuộc tính nội suy được nội suy như các hàm nội suy thông thường.
5. Hạn chế cho các hàm nội suy API công khai
Khi một hàm nội suy là public
hoặc protected
nhưng không phải là một phần của một khai báo private
hoặc internal
, nó được coi là một API công khai của module. Nó có thể được gọi trong các module khác và cũng được nội suy tại các điểm gọi như vậy.
Điều này đặt ra một số rủi ro về không tương thích nhị phân do các thay đổi trong module khai báo một hàm nội suy trong trường hợp module gọi không được biên dịch lại sau sự thay đổi.
Để loại bỏ rủi ro không tương thích như vậy được giới thiệu bởi một thay đổi trong một API không công khai của một module, các hàm nội suy API công khai không được phép sử dụng các khai báo không công khai, tức là các khai báo private
và internal
và các phần của chúng, trong thân hàm của chúng.
Một khai báo internal
có thể được đánh dấu bằng @PublishedApi
, điều này cho phép sử dụng nó trong các hàm nội suy API công khai. Khi một hàm nội suy internal
được đánh dấu là @PublishedApi
, thân hàm của nó cũng được kiểm tra, như là nó là công khai.
Cảm ơn bạn đã thăm Cafedev để cùng chúng tôi khám phá sức mạnh của Kotlin và Inline Functions. Hy vọng rằng thông tin chúng tôi chia sẻ đã mang lại những hiểu biết mới và hữu ích cho bạn trong lập trình. Đừng ngần ngại tiếp tục đặt câu hỏi và thảo luận tại cộng đồng Cafedev – nơi gặp gỡ, chia sẻ kiến thức và tạo ra những ý tưởng sáng tạo. Hãy tiếp tục đồng hành cùng Cafedev, nơi mà sự học hỏi không bao giờ có điểm dừng!”
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!