[譯] Kotlin 揭祕:理解並速記 Lambda 語法

LeviDing發表於2018-07-26

在奧地利旅行期間,我參觀了維也納的奧地利國家圖書館。特別是國會大廳,這個令人驚歎的空間感覺就像印第安納瓊斯電影中的一些東西。房間周圍的空間是這些門被裝在架子上,很容易想象它們背後隱藏著什麼樣的祕密。

然而,事實證明,它們只是簡單的圖書館。

讓我們假設我們有一個應用程式來跟蹤庫中的書籍。有一天,我們想知道這個系列中最長和最短的書是什麼。之後,我們編寫程式碼,允許我們找到這兩個:

val shortestBook = library.minBy { it.pageCount }val longestBook = library.maxBy { it.pageCount }
複製程式碼

完美!但這讓我感到疑惑,這些方法是如何工作的?it 是怎麼知道的,只是寫了 it.pageCount,到底該怎麼做呢?

我做的第一件事就是定義 minBymaxBy,這兩者都是在 Collections.kt。由於它們幾乎完全相同,所以讓我們來看看 maxBy,它從 1559 行開始。

那裡的方法是在 [Iterable](https://developer.android.com/reference/java/lang/Iterable) 介面上構建的,但是如果我們做一個小的重寫來使用[Collection](https://developer.android.com/reference/java/util/Collection)s,也許將一些變數的重新命名變的更冗長,更容易理解:

public inline fun <T, R : Comparable<R>> Collection<T>.maxBy(selector: (T) -> R): T? {
    if (isEmpty()) return null
    var maxElement = first()
    var maxValue = selector(maxElement)
    for (element in this) {
        val value = selector(element)
        if (maxValue < value) {
            maxElement = element
            maxValue = value
        }
    }
    return maxElement
}
複製程式碼

我們可以看到它只是在 Collection 中獲取每個元素,檢查來自 selector 的值是否大於它看到的最大值。如果是,則儲存元素和值。最後,它返回它找到的最大元素。相當簡單。

然而 selector,看起來很整潔,它必須是允許我們在上面使用 it.pageCount 的東西,所以讓我們再看看它。

即使只是在這一行中,甚至還有相當多的語法糖。在這種情況下,對於 selector: (T) -> R 來說是一個帶有單個引數 T 的函式,並返回一些型別 R 相關的返回值。

可行的方法是 Kotlin 包含一組名為 FunctionN 的介面,其中 N 是它接受的引數數量。由於我們有一個引數,我們可以實現 Function1 介面,然後在我們的程式碼中使用它:

class BookSelector : Function1<Book, Int> {
   override fun invoke(book: Book): Int {
       return book.pageCount
   }
}
 
val longestBook = library.maxBy(BookSelector())
複製程式碼

這無疑顯示了它的工作原理。selector 是一個 Function1,當給定 Book 時,返回一個 Int。然後,maxBy 獲取 Int 並將其與它具有的值進行比較。

順便說一句,這也解釋了為什麼泛型引數 R 具有型別 R [implements] Comparable <R>。如果 R 不是 Comparable,我們不能做 if(maxValue <value)

接下來的問題是,我們如何從那開始,到我們開始的一個迴圈?讓我們逐步完成整個過程。

首先,程式碼可以替換為 lambda,它已經減少了很多:

val longestBook = library.maxBy({
    it.pageCount
})
複製程式碼

下一步是如果方法的最後一個引數是 lambda,我們可以關閉括號,然後將 lambda 新增到行的末尾,如下所示:

val longestBook = library.maxBy() {
    it.pageCount
}
複製程式碼

最後,如果一個方法只接受一個 lambda 引數,我們就可以完全放棄 () 方法,這會讓我們回到初始程式碼:

val longestBook = library.maxBy { it.pageCount }
複製程式碼

但是等等!那個 Function1 要怎麼樣!我每次使用它時都會執行分配嗎?

這是一個很好的問題!好訊息是,不,你不是。如果你再看一遍,你會看到它 maxBy 被標記為一個 inline 函式。這在編譯期時會在源級別發生,因此雖然編譯的程式碼比最初看起來的樣本多,但是沒有任何顯著的效能影響,當然也沒有物件分配。

真棒!現在,我們不僅知道圖書館中最短(也是最長)的書籍,我們還能更好地理解 maxBy 它是如何工作的。我們看到 Kotlin 如何使用[FunctionN](#full) lambda 的介面,以及如何將 lambda 表示式移到函式的引數列表之外。最後,我們知道,當只有一個 lambda 引數呼叫函式時,可以完全省略通常使用的括號

檢視 Google Developers 部落格,瞭解更多精彩內容,敬請期待更多關於 Kotlin 的文章!

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章