+1

Hàm tham chiếu trong Kotlin: sử dụng hàm như một lambdas khắp nơi

Hàm tham chiếu là sự khác biệt và có bước cải tiến lớn trong Kotlin mà Java không có. Nếu bạn đã từng nghiên cứu hoặc làm việc với Kotlin rồi thì bạn sẽ thấy rằng ** Kotlin hỗ trợ một hàm như một kiểu** tức là Kotlin coi mỗi hàm của chúng ta như một kiểu dữ liệu thông thường, có thể sử dụng nó như một tham số cho một hàm khác hoặc 1 hàm có trả về một hàm. Đó chính là lý do chính để xem xét việc ngôn ngữ có hỗ trợ phong cách lập trình hướng hàm hay không, và Kotlin chắc chắn cho phép bạn làm điều đó. Bạn có thể khai báo hàm như một biến như sau

val sum: (Int, Int) -> Int = { x, y -> x + y }

Bạn thấy đó, Hàm này sẽ có tham số là 2 số nguyên và trả về một số nguyên, cụ thể hơn lambdas được áp dụng vào việc khai báo này. Bạn có thể sử dụng lambdas như là một tham số trong một hàm

	fun applyOp(x: Int, y: Int, op: (Int, Int) -> Int): Int = op(x, y)

ở hàm này sử dụng 2 số nguyên làm tham số và áp dụng hàm cho cả hai. bạn có thể xem sử kết hợp của 2 cách này

applyOp(2, 3, sum)

Thật dễ dàng phải không nào? Bạn có thể đã biết những thứ tôi vừa nói phía trên rồi. Nhưng tôi sẽ cho bạn xem những thứ thú vị ngay sau đây:

Hàm tham chiếu: mỗi hàm có thể là một lambdas

Giống như cách lambdas truyền tham số hoặc lưu biến, chúng ta có thể làm điều tương tự với những hàm thông thường. Cảm ơn hàm tham chiếu, nhờ nó mà code của chúng ta sẽ trở lên sạch hơn và chúng ta có thể áp dụng phong cách hướng lập trình hàm này vào các thư viện hoặc frameworks Đầu tiên tôi sẽ giải thích với ví dụ phía trên. Tưởng tượng nó sẽ thay thế lambdas như sau:

fun sum(x: Int, y: Int) = x + y

Việc định nghĩa như này sẽ tương tự ví dụ ban đầu tôi đề cập, nhưng hay vì có một biến giữ tham chiếu của hàm, mà chúng ta chỉ có hàm không thôi. Giờ nếu bạn gọi theo cách cũ, nó sẽ lỗi

applyOp(2, 3, sum)

sum không được phát hiện là kiểu lambdas, mà sum chỉ là một hàm thông thường, nhưng nếu bạn suy nghĩ một cách kỹ lưỡng hơn, thì hàm này nó có chung cấu trúc, nó nhận thâm số là 2 số nguyên và trả về 1 số nguyên. Vậy điều gì chúng ta đã bỏ lỡ ở đây? Ở đây chúng ta phải sử dụng hàm tham chiếu, bạn cần phải thêm 2 dấu : trước tên của hàm

applyOp(2, 3, ::sum)

Xong rồi!

Hàm tham chiếu có hành vi như một lambdas như việc bạn có thể gán 1 tham chiếu cho 1 biến :

val sumLambda: (Int, Int) -> Int = ::sum

Tuyệt vời không? Nhưng hãy cùng xem một số ví dụ thiết thực hơn phía dưới này nhé

Thực hàm tham chiếu

Ví dụ bạn có một hàm nhận data và update UI, nhưng bạn chỉ cần update trong trường hợp dữ liệu khác NULL. Phần lớn chúng ta sẽ làm theo cách sau:

private fun updateUI(data: Data?) {
    if(data != null){
        applyUiChanges(data)
    }
}
 
private fun applyUiChanges(data: Data) {
    // Do cool stuff in UI
}

Nếu bạn đã làm Kotlin rồi thì có thể bạn sẽ biết hàm let. Bạn sẽ đồng ý với tôi khi dùng let code của bạn sẽ trở thành như thế này

data?.let { applyUiChanges(data) }

Những hãy thử nghĩ một chút. Nếu bạn kiểm tra cấu trúc của let ( đừng lo lắng nếu bạn không hiểu gì nhé 😃)

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

Hàm này nhận lambdas với 2 tham số, T và R. Cụ thể hơn T là Data và R là Unit, vì ta không trả về gía trị của let . Vì nó có thể được trông thấy như sau nếu cụ thể hóa generic type

public inline fun let(block: (Data) -> Unit) = block(this)

Hàm applyUiChanges nó nhận một Data và không trả về gì. (Unit) . vì thế nó rất phù hợp với kiểu cấu trúc của hàm let , và tại sao chúng ta lại không gọi làm này thay thế?

data?.let(this::applyUiChanges)

Nguyên tắc: Khi trong lambdas chúng ta sử dụng hàm mà nhận nhiều giá trị mà có chưa lambdas, thì bạn nên sử dụng hàm tham chiếu

Lambdas mới nhiều hơn một tham số

Xem ví dụ sau

var items: List<MediaItem> by Delegates.observable(emptyList()) { property, oldValue, newValue ->
    notifyChanges(oldValue, newValue)
}

Bạn có thể thấy, chúng ta không thể tham chiếu tới hàm bởi vì notifyChanges không có chung cấu trúc. Nó chỉ nhận 2 giá trị thay vì 3. Nhưng chúng ta có thể thay đổi một chút

fun notifyChanges(property: KProperty<*>, oldValue: List<MediaItem>,
                          newValue: List<MediaItem>) {
    ...
}

Giờ thì ta có thể sử dụng tham chiếu thay thế

var items: List<MediaItem> by Delegates.observable(emptyList(), ::notifyChanges)

Tham chiếu thuộc tính

Tham chiếu không chỉ dừng lại ở function mà bạn có thể dùng nó cho thuộc tính

data class MediaItem(val title: String, val url: String)

sử dụng

items
    .sortedBy(MediaItem::title)
    .map(MediaItem::url)
    .forEach(::println)

với lambdas

items
    .sortedBy { it.title }
    .map { it.url }
    .forEach { print(it) }

Qua bài viết bày tôi mong các bạn có thêm một cách làm mới với hàm tham chiếu, biết cách sử dụng hiệu quả trong công việc của mình Tham khảo Function references in Kotlin


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí