Kotlin中l(wèi)et、run、with、apply及also的用法和差別
前言
作用域函數(shù)是Kotlin比較重要的一個特性,共分為以下5種:let、run、with、apply 以及 also,這五個函數(shù)的工作方式可以說非常相似,但是我們需要了解的是這5種函數(shù)的差異,以便在不同的場景更好的利用它。讀完這篇文章您將了解到:
什么是Kotlin的作用域函數(shù)?
let、run、with、apply 以及 also這5種作用域函數(shù)各自的角色定位;
5種作用域函數(shù)的差異區(qū)分;
何時何地使用這5種作用域?
Kotlin的作用域函數(shù)
Kotlin 標準庫包含幾個函數(shù),它們的唯一目的是在對象的上下文中執(zhí)行代碼塊。當對一個對象調(diào)用這樣的函數(shù)并提供一個 lambda 表達式時,它會形成一個臨時作用域。在此作用域中,可以訪問該對象而無需其名稱。這些函數(shù)稱為作用域函數(shù)。
簡單來說,作用域函數(shù)是為了方便對一個對象進行訪問和操作,你可以對它進行空檢查或者修改它的屬性或者直接返回它的值等操作,下面提供了案例對作用域函數(shù)進行了詳細說明。
角色定位
let
public inline fun <T, R> T.let(block: (T) -> R): R
let函數(shù)是參數(shù)化類型 T 的擴展函數(shù)。在let塊內(nèi)可以通過 it 指代該對象。返回值為let塊的最后一行或指定return表達式。
我們以一個Book對象為例,類中包含Book的name和price,如下:
class Book() { var name = "《數(shù)據(jù)結(jié)構(gòu)》" var price = 60 fun displayInfo() = print("Book name : $name and price : $price") }
fun main(args: Array<String>) { val book = Book().let { it.name = "《計算機網(wǎng)絡》" "This book is ${it.name}" } print(book) } 控制臺輸出: This book is 《計算機網(wǎng)絡》
在上面案例中,我們對Book對象使用let作用域函數(shù),在函數(shù)塊的最后一句添加了一行字符串代碼,并且對Book對象進行打印,我們可以看到最后控制臺輸出的結(jié)果為字符串“This book is 《計算機網(wǎng)絡》”。
按照我們的編程思想,打印一個對象,輸出必定是對象,但是使用let函數(shù)后,輸出為最后一句字符串。這是由于let函數(shù)的特性導致。因為在Kotlin中,如果let塊中的最后一條語句是非賦值語句,則默認情況下它是返回語句。
那如果我們將let塊中最后一條語句修改為賦值語句,會發(fā)生什么變化?
fun main(args: Array<String>) { val book = Book().let { it.name = "《計算機網(wǎng)絡》" } print(book) } 控制臺輸出: kotlin.Unit
可以看到我們將Book對象的name值進行了賦值操作,同樣對Book對象進行打印,但是最后控制臺的輸出結(jié)果為“kotlin.Unit”,這是因為在let函數(shù)塊的最后一句是賦值語句,print則將其當做是一個函數(shù)來看待。
這是let角色設定的第一點
let塊中的最后一條語句如果是非賦值語句,則默認情況下它是返回語句,反之,則返回的是一個Unit類型。
我們來看let的第二點
let可用于空安全檢查。
如需對非空對象執(zhí)行操作,可對其使用安全調(diào)用操作符 ?. 并調(diào)用let在lambda表達式中執(zhí)行操作。如下案例:
var name: String? = null fun main(args: Array<String>) { val nameLength = name?.let { it.length } ?: "name為空時的值" print(nameLength) }
我們設置name為一個可空字符串,利用name?.let來進行空判斷,只有當name不為空時,邏輯才能走進let函數(shù)塊中。在這里,我們可能還看不出來let空判斷的優(yōu)勢,但是當你有大量name的屬性需要編寫的時候,就能發(fā)現(xiàn)let的快速和簡潔。
let的第三點
let可對調(diào)用鏈的結(jié)果進行操作。
關(guān)于這一點,官方教程給出了一個案例,在這里就直接使用:
fun main(args: Array<String>) { val numbers = mutableListOf("One","Two","Three","Four","Five") val resultsList = numbers.map { it.length }.filter { it > 3 } print(resultsList) }
我們的目的是獲取數(shù)組列表中長度大于3的值。因為我們必須打印結(jié)果,所以我們將結(jié)果存儲在一個單獨的變量中,然后打印它。但是使用“let”操作符,我們可以將代碼修改為:
fun main(args: Array<String>) { val numbers = mutableListOf("One","Two","Three","Four","Five") numbers.map { it.length }.filter { it > 3 }.let { print(it) } }
使用let后可以直接對數(shù)組列表中長度大于3的值進行打印,去掉了變量賦值這一步。
另外,let函數(shù)還存在一個特點。
let的第四點
let可以將“It”重命名為一個可讀的lambda參數(shù)。
let是通過使用“It”關(guān)鍵字來引用對象的上下文,因此,這個“It”可以被重命名為一個可讀的lambda參數(shù),如下將it重命名為book:
fun main(args: Array<String>) { val book = Book().let {book -> book.name = "《計算機網(wǎng)絡》" } print(book) }
run
run函數(shù)以“this”作為上下文對象,且它的調(diào)用方式與let一致。
另外,第一點:當 lambda 表達式同時包含對象初始化和返回值的計算時,run更適合。
這句話是什么意思?我們還是用案例來說話:
fun main(args: Array<String>) { Book().run { name = "《計算機網(wǎng)絡》" price = 30 displayInfo() } } 控制臺輸出: Book name : 《計算機網(wǎng)絡》 and price : 30
如果不使用run函數(shù),相同功能下代碼會怎樣?來看一看:
fun main(args: Array<String>) { val book = Book() book.name = "《計算機網(wǎng)絡》" book.price = 30 book.displayInfo() } 控制臺輸出: Book name : 《計算機網(wǎng)絡》 and price : 30
輸出結(jié)果還是一樣,但是run函數(shù)所帶來的代碼簡潔程度已經(jīng)顯而易見。
除此之外,讓我們來看看run函數(shù)的其他優(yōu)點。通過查看源碼,了解到run函數(shù)存在兩種聲明方式。
1、與let一樣,run是作為T的擴展函數(shù)。
inline fun <T, R> T.run(block: T.() -> R): R
2、第二個run的聲明方式則不同,它不是擴展函數(shù),并且塊中也沒有輸入值,因此,它不是用于傳遞對象并更改屬性的類型,而是可以使你在需要表達式的地方就可以執(zhí)行一個語句。
inline fun <R> run(block: () -> R): R
如下利用run函數(shù)塊執(zhí)行方法,而不是作為一個擴展函數(shù)。
run { val book = Book() book.name = "《計算機網(wǎng)絡》" book.price = 30 book.displayInfo() }
with
inline fun <T, R> with(receiver: T, block: T.() -> R): R
with屬于非擴展函數(shù),直接輸入一個對象receiver,當輸入receiver后,便可以更改receiver的屬性,同時,它也與run做著同樣的事情。
還是提供一個案例說明:
fun main(args: Array<String>) { val book = Book() with(book) { name = "《計算機網(wǎng)絡》" price = 40 } print(book) }
以上面為例,with(T)類型傳入了一個參數(shù)book,則可以在with的代碼塊中訪問book的name和price屬性,并做更改。
with使用的是非null的對象,當函數(shù)塊中不需要返回值時,可以使用with。
apply
inline fun <T> T.apply(block: T.() -> Unit): T inline fun <T> T.apply(block: T.() -> Unit): T
apply是 T 的擴展函數(shù),與run函數(shù)有些相似,它將對象的上下文引用為“this”而不是“it”,并且提供空安全檢查,不同的是,apply不接受函數(shù)塊中的返回值,返回的是自己的T類型對象。
fun main(args: Array<String>) { Book().apply { name = "《計算機網(wǎng)絡》" price = 40 } print(book) } 控制臺輸出: com.fuusy.kotlintest.Book@61bbe9ba
前面看到的 let、with 和 run 函數(shù)返回的值都是 R。但是,apply 和下面查看的 also 返回 T。例如,在 let 中,沒有在函數(shù)塊中返回的值,最終會成為 Unit 類型,但在 apply 中,最后返回對象本身 (T) 時,它成為 Book 類型。
apply函數(shù)主要用于初始化或更改對象,因為它用于在不使用對象的函數(shù)的情況下返回自身。
also
inline fun <T> T.also(block: (T) -> Unit): T
also是 T 的擴展函數(shù),返回值與apply一致,直接返回T。also函數(shù)的用法類似于let函數(shù),將對象的上下文引用為“it”而不是“this”以及提供空安全檢查方面。
因為T作為block函數(shù)的輸入,可以使用also來訪問屬性。所以,在不使用或不改變對象屬性的情況下也使用also。
fun main(args: Array<String>) { val book = Book().also { it.name = "《計算機網(wǎng)絡》" it.price = 40 } print(book) } 控制臺輸出: com.fuusy.kotlintest.Book@61bbe9ba
差異化
let & run
let將上下文對象引用為it ,而run引用為this;
run無法將“this”重命名為一個可讀的lambda參數(shù),而let可以將“it”重命名為一個可讀的lambda參數(shù)。在let多重嵌套時,就可以看到這個特點的優(yōu)勢所在。
with & run
with和run其實做的是同一種事情,對上下文對象都稱之為“this”,但是他們又存在著不同,我們來看看案例。
先使用with函數(shù):
fun main(args: Array<String>) { val book: Book? = null with(book){ this?.name = "《計算機網(wǎng)絡》" this?.price = 40 } print(book) }
我們創(chuàng)建了一個可空對象book,利用with函數(shù)對book對象的屬性進行了修改。代碼很直觀,那么我們接著將with替換為run,代碼更改為:
fun main(args: Array<String>) { val book: Book? = null book?.run{ name = "《計算機網(wǎng)絡》" price = 40 } print(book) }
首先run函數(shù)的調(diào)用省略了this引用,在外層就進行了空安全檢查,只有非空時才能進入函數(shù)塊內(nèi)對book進行操作。相比較with來說,run函數(shù)更加簡便,空安全檢查也沒有with那么頻繁。
apply & let
apply不接受函數(shù)塊中的返回值,返回的是自己的T類型對象,而let能返回。
apply上下文對象引用為“this”,let為“it”。
何時應該使用它們
用于初始化對象或更改對象屬性,可使用apply
如果將數(shù)據(jù)指派給接收對象的屬性之前驗證對象,可使用also
如果將對象進行空檢查并訪問或修改其屬性,可使用let
如果是非null的對象并且當函數(shù)塊中不需要返回值時,可使用with
如果想要計算某個值,或者限制多個本地變量的范圍,則使用run
小結(jié)
以上便是Kotlin作用域函數(shù)的作用以及使用場景,在Android實際開發(fā)中,5種函數(shù)使用的頻次非常高,在使用過程中發(fā)現(xiàn),當代碼邏輯少的時候,作用域函數(shù)能帶給我們代碼的簡潔性可讀性,但是當邏輯復雜時,使用不同的函數(shù),多次疊加都將降低可讀性。這就要我們?nèi)^(qū)分它們各自的特點,以便在適合且復雜的場景下去使用它。
到此這篇關(guān)于Kotlin中l(wèi)et、run、with、apply及also的用法和差別的文章就介紹到這了,更多相關(guān)Kotlin中l(wèi)et、run、with、apply also內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot中使用Redisson+AOP+自定義注解實現(xiàn)訪問限流與黑名單攔截
本文主要介紹了Springboot中使用Redisson+AOP+自定義注解實現(xiàn)訪問限流與黑名單攔截,包含針對用戶IP限流,整個接口的訪問限流,以及對某個參數(shù)字段的限流,并且支持請求限流后處理回調(diào),感興趣的可以了解一下2024-02-02java根據(jù)當前時間獲取yyyy-MM-dd?HH:mm:ss標準格式的時間代碼示例
在Java中可以使用java.time包中的LocalDateTime類和DateTimeFormatter類來獲取并格式化當前時間為yyyy-MM-dd?HH:mm:ss的格式,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-10-10Gradle進階使用結(jié)合Sonarqube進行代碼審查的方法
今天小編就為大家分享一篇關(guān)于Gradle進階使用結(jié)合Sonarqube進行代碼審查的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12解決SpringMVC @RequestMapping不設置value出現(xiàn)的問題
這篇文章主要介紹了解決SpringMVC @RequestMapping不設置value出現(xiàn)的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08