柚子快報邀請碼778899分享:76.Kotlin擴展函數(shù)簡介
第一部分:Kotlin 擴展函數(shù)簡介
Kotlin 語言自帶很多擴展函數(shù),它們通過在已有類型上擴展新的作用域函數(shù)(scope function)來改善代碼可讀性與簡潔性。這些擴展函數(shù)不僅能夠改善代碼風格,同時還能提供鏈式調(diào)用的便利。常見的擴展函數(shù)有:
letalsoapplyrunwith
這些擴展函數(shù)可以讓我們在對象上執(zhí)行一些額外操作,同時還允許我們避免樣板代碼(boilerplate)和顯式的數(shù)據(jù)傳遞。每個函數(shù)在設計時都有其側重點,以下將分別介紹。
第二部分:let 函數(shù)
2.1 let 函數(shù)的基本概念
let 是 Kotlin 中最常使用的擴展函數(shù)之一,其標準定義如下(簡化示意):
public inline fun
簡單說,let 接受調(diào)用對象作為接收者,但在 lambda 表達式中,它以參數(shù) it(如果不定義參數(shù)名的話)傳入。let 會返回 lambda 表達式中的最后一行表達式的值。這說明它的返回值由 lambda 決定。
2.2 let 的參數(shù)傳遞方式
在 let 中,調(diào)用對象會被作為 lambda 的唯一參數(shù)傳遞進去,該參數(shù)默認命名為 it(你也可以自己命名)。因此在 lambda 內(nèi)部,若你需要引用調(diào)用對象,就需要使用 it。例如:
val name = "Alice"
val length = name.let {
println("當前字符串:$it")
// 返回 it 的長度
it.length
}
在這個例子中,字符串 "Alice" 就是 let 調(diào)用的對象,并以 it 的形式傳入 lambda,最后返回值為 length。
2.3 let 的使用場景
非空判斷與安全調(diào)用 let 常常與空安全操作符(?.)搭配使用,可以避免空指針異常。例如:
val nullableName: String? = "Bob"
nullableName?.let {
println("非空字符串:$it")
// 可在空不為null時執(zhí)行更多操作
}
局部變量的作用域限制 let 函數(shù)可以用來將一段代碼包裹在一個局部作用域內(nèi),從而避免在代碼中引入過多的局部變量。 鏈式調(diào)用中的轉換 可以使用 let 在鏈式調(diào)用中轉換某個值,而不影響后續(xù)鏈式調(diào)用,比如計算值并返回最后結果。
2.4 let 的代碼示例
下面是一份完整的 Kotlin 文件,展示了 let 的使用以及詳細注釋說明:
package com.example.extensions
fun main() {
// 示例1:對非空字符串執(zhí)行操作
val name: String? = "Alice"
name?.let { nonNullName ->
println("Inside let: 當前字符串是 $nonNullName")
// 返回處理結果,這里返回的是字符串長度
val length = nonNullName.length
println("字符串長度為 $length")
}
// 示例2:用于鏈式調(diào)用,轉換數(shù)據(jù)類型
val originalValue = "12345"
val processedValue: Int = originalValue.let {
println("轉換前的字符串:$it")
// 可以做一些轉換操作,比如轉換成 Int
it.toIntOrNull() ?: 0
}
println("轉換后的整型值為 $processedValue")
// 示例3:用作局部計算
val result = "Hello, World!".let {
println("字符串是:$it")
// 可以返回任意類型
"$it - Processed"
}
println("let 返回的結果:$result")
}
2.5 總結 let
let 的參數(shù)以 it 的形式傳遞,也可以指定名稱。返回 lambda 表達式最后一行的值。主要用途在于進行空值判斷、值轉換和局部作用域的封裝。
第三部分:also 函數(shù)
3.1 also 函數(shù)的基本概念
also 的定義如下:
public inline fun
與 let 類似,also 通過 lambda 的參數(shù) it 接收對象。但與 let 不同的是,also 返回調(diào)用對象本身,而非 lambda 中最后行的值。因此也適用于鏈式調(diào)用。主要用于執(zhí)行額外操作,例如日志記錄、調(diào)試信息、或?qū)φ{(diào)用對象進行一些額外不影響主要流程的處理。
3.2 also 的參數(shù)傳遞方式
在 also 中,調(diào)用對象同樣作為參數(shù)傳遞至 lambda 中,默認名稱為 it。例如:
val number = 42
val sameNumber = number.also {
println("Logging number: $it")
}
這里,it 表示調(diào)用該擴展函數(shù)的對象,而整個 also 調(diào)用最終返回 number 本身。
3.3 also 的使用場景
調(diào)試和日志記錄 also 常用于插入調(diào)試代碼或日志記錄,不改變對象數(shù)據(jù),但讓鏈式調(diào)用更易于閱讀。 附加操作 當需要在對象上執(zhí)行額外操作,而不期望返回值變化時,使用 also 是理想的選擇。例如,在對象構造完成時記錄信息,再繼續(xù)使用該對象。
3.4 also 的代碼示例
如下代碼展示了 also 的一般用法:
package com.example.extensions
data class User(var username: String, var age: Int)
fun main() {
// 創(chuàng)建一個 User 對象并在 also 中記錄日志
val user = User("Bob", 25).also {
println("創(chuàng)建 User 對象:$it")
}
// 也可以在鏈式調(diào)用中使用 also 保存中間結果
val result = "Kotlin".also {
println("原始字符串 $it")
}.uppercase()
println("經(jīng) uppercase 后結果:$result")
}
3.5 總結 also
參數(shù)采用 it 傳遞,表示當前對象。用于執(zhí)行額外操作(如日志記錄、驗證等)。返回對象本身以便于鏈式調(diào)用,不會改變原始對象的值。
第四部分:apply 函數(shù)
4.1 apply 函數(shù)的基本概念
apply 是一個非常常用的擴展函數(shù),其定義如下:
public inline fun
在 apply 中,調(diào)用對象作為 lambda 表達式的接收者(即 this),而不是作為參數(shù)傳遞。這意味著你可以直接使用 this 來訪問對象的屬性和方法,而且通??梢允÷?this 關鍵字,因為在 lambda 內(nèi)部默認就是當前對象。apply 總是返回調(diào)用對象本身,非常適合于對象的初始化或配置。
4.2 apply 的參數(shù)傳遞方式
在 apply 中,lambda 的接收者為調(diào)用 apply 的對象。也就是說,lambda 內(nèi)部可以直接訪問對象的屬性和方法,而不用通過 it 來調(diào)用。例如:
val person = Person().apply {
name = "Charlie"
age = 30
}
在這里,name 和 age 是直接設置的,因為 this 被隱式地引用為 Person 對象。
4.3 apply 的使用場景
對象初始化和配置 apply 最常見場景就是在創(chuàng)建對象后對其屬性進行初始化或配置。這樣可以減少樣板代碼并提高代碼可讀性。 構建對象 當構建一個復雜對象時,可以利用 apply 進行屬性賦值操作,并返回構建好的對象。
4.4 apply 的代碼示例
下面給出一個完整描述 apply 典型用法的示例代碼:
package com.example.extensions
data class Person(var name: String = "", var age: Int = 0)
fun main() {
// 使用 apply 配置 Person 對象
val person = Person().apply {
// 在 lambda 內(nèi)部直接訪問對象的屬性,這里的 this 指代 Person 對象
name = "Diana"
age = 28
println("Inside apply: name = $name, age = $age")
}
println("Configured Person: $person")
// 常用于鏈式調(diào)用初始化
val list = mutableListOf
add(1)
add(2)
add(3)
println("Inside apply, list: $this")
}
println("Final list: $list")
}
4.5 總結 apply
使用 this 作為 lambda 接收者,可以直接訪問對象的屬性。主要用于對象初始化和配置,不改變返回值,始終返回同一對象。應用場景包括構建對象、設置屬性等操作。
第五部分:run 函數(shù)
5.1 run 函數(shù)的基本概念
run 是一個有兩種形式的擴展函數(shù)。擴展形式如下:
public inline fun
其非擴展形式則是例如:
public inline fun
在擴展形式中,run 與 apply 類似,也使用 this 作為 lambda 的接收者,但不同的是 run 返回 lambda 的最后一行表達式結果,而不是調(diào)用對象本身;這使得 run 更適合執(zhí)行一些計算,然后返回一個值。
在非擴展形式中,run 可以用來執(zhí)行一段代碼塊,并返回計算結果,適用于那些需要將局部代碼塊的結果傳遞出去的場景。
5.2 run 的參數(shù)傳遞方式
在擴展形式的 run 中,lambda 接收者為調(diào)用對象,使用 this 來引用對象,可以省略顯式的 this。在非擴展形式中,代碼塊沒有接收者,純粹執(zhí)行一段代碼并返回值。
例如擴展形式:
val person = Person("Eric", 35)
val description = person.run {
// this 默認為 person
"Person: name = $name, age = $age"
}
非擴展形式:
val sum = run {
val a = 10
val b = 20
a + b // 返回計算結果 30
}
5.3 run 的使用場景
對象上下文下計算結果 當需要在對象上下文中計算并返回一個結果時,用 run 更適合。例如,對象的狀態(tài)運算或格式化結果返回。 臨時作用域封裝 非擴展版的 run 可以作為一個代碼塊,把局部變量和計算封裝起來,防止污染外部作用域,并返回一個計算結果。
5.4 run 的代碼示例
下面展示 run 的擴展和非擴展使用場景:
package com.example.extensions
data class Person(var name: String, var age: Int)
fun main() {
// 擴展形式 run:使用對象的上下文,返回 lambda 最后一行的值
val person = Person("Frank", 40)
val info: String = person.run {
println("Inside run: name = $name, age = $age")
"Person info: $name ($age years old)"
}
println("Run result: $info")
// 非擴展形式 run:執(zhí)行一段代碼塊,返回代碼塊的計算結果
val result: Int = run {
val x = 100
val y = 50
println("Inside plain run: x = $x, y = $y")
x - y
}
println("Plain run result: $result")
}
5.5 總結 run
擴展形式:與 apply 類似,傳入對象上下文,返回 lambda 計算結果。非擴展形式:無接收者,僅執(zhí)行一段代碼塊并返回結果。適用于需要在對象上下文中計算并返回結果或封裝一段獨立邏輯的場景。
第六部分:with 函數(shù)
6.1 with 函數(shù)的基本概念
with 是 Kotlin 中的一個標準庫函數(shù),其定義如下:
public inline fun
與 run 類似,with 將傳入的對象作為 lambda 的接收者(this)提供給代碼塊,但它不是作為擴展函數(shù)調(diào)用,而是一個普通函數(shù)。它返回 lambda 表達式最后一行的結果。
6.2 with 的參數(shù)傳遞方式
在 with 中,第一個參數(shù)是傳入的對象,作為 lambda 的接收者(this)。在 lambda 內(nèi)部,不像 let 或 also 那樣依賴 it,而是通過 this(可以省略)直接訪問對象。
例如:
val person = Person("Grace", 27)
val result = with(person) {
println("Accessing within with: name = $name, age = $age")
"Person details: $name is $age years old"
}
6.3 with 的使用場景
對象操作封裝 當需要對同一個對象進行多次操作,且返回計算結果時 with 是一種不錯的選擇。與 run 的擴展形式極為相似,但區(qū)別在于 with 是一個普通函數(shù),傳入對象后再調(diào)用代碼塊,可以讓代碼邏輯更清晰。 避免重復引用對象 在代碼塊內(nèi)直接使用對象,各個屬性及方法都可以直接操作,避免重復的對象引用。
6.4 with 的代碼示例
下面給出一個完整的示例,展示 with 的使用方法:
package com.example.extensions
data class Person(var name: String = "", var age: Int = 0)
fun main() {
val person = Person("Helen", 33)
// 使用 with 來對 person 對象進行多次操作并返回計算結果
val detail = with(person) {
println("Inside with: name = $name, age = $age")
// 返回一個描述字符串
"Person Details: $name is $age years old."
}
println("With result: $detail")
}
6.5 總結 with
通過普通函數(shù)傳入對象作為 receiver,將對象作為 lambda 中的 this。與 run(擴展版)功能相似,但使用方式略有不同。適合用于封裝對單個對象的多次操作,并返回計算結果。
第七部分:各擴展函數(shù)的對比與區(qū)別
經(jīng)過對 let、also、apply、run 和 with 的講解,我們可以總結出各擴展函數(shù)主要的區(qū)別如下:
擴展函數(shù)參數(shù)傳遞方式返回值主要用途常見場景l(fā)et以 it 傳遞Lambda 最后行值對對象進行轉換、處理、非空安全操作處理可空對象、數(shù)據(jù)轉換、局部變量作用域also以 it 傳遞調(diào)用對象本身執(zhí)行副作用,比如日志、調(diào)試及額外操作記錄調(diào)試信息、檢查值后返回原對象apply以 this 傳遞調(diào)用對象本身對對象進行初始化或配置構建或設置對象屬性、初始化配置run以 this 傳遞Lambda 最后行值執(zhí)行代碼塊并返回計算結果對象上下文下的結果計算、封裝代碼塊with以 this 傳遞Lambda 最后行值同 run,但非擴展函數(shù)調(diào)用組合操作同一對象,減少重復對象引用
參數(shù)區(qū)別
let 與 also 中,lambda 函數(shù)的參數(shù)默認命名為 it,這樣在 lambda 內(nèi)部引用調(diào)用對象須寫成 it.someMethod() 或 it.propertyapply、run(擴展版)和 with 中,lambda 內(nèi)部的 this 隱式代表調(diào)用對象,可以直接調(diào)用或讀取對象的屬性,而無需額外的參數(shù)前綴。注意:在 apply 中,由于是擴展函數(shù),傳入的 lambda 表達式不會返回新值,而是始終返回調(diào)用對象本身;而 run 和 with 返回的是 lambda 表達式最后一行的結果。
使用場景比較
當你需要對一個可能為 null 的對象進行安全調(diào)用時,let 是最佳選擇;如果你只是想在不改變對象的情況下監(jiān)控或記錄一些值,那么 also 很適合;當你創(chuàng)建一個對象并需要在同一代碼塊中配置多個屬性時,apply 極大地提升代碼可讀性,避免重復引用變量;如果你需要在對象上下文下計算某個表達式,并返回一個結果,run(擴展版)會使代碼更為流暢;如果不想鏈式調(diào)用,但需要對一個對象執(zhí)行多個操作而最終返回一個值,with 是個不錯的選擇。
第八部分:綜合示例與實際應用
為了將上述擴展函數(shù)的概念與使用場景更好地結合起來,我們將用一個復雜點的示例來綜合展示它們。假設我們正在構建一個簡單的用戶注冊系統(tǒng),需要對用戶對象進行配置、檢測與最終返回注冊信息。
我們通過以下完整的 Kotlin 文件展示如何采用各種擴展函數(shù)來完成工作,并在每一步詳細注釋說明每種擴展函數(shù)的作用和使用場景。
package com.example.registration
data class User(
var username: String = "",
var email: String = "",
var age: Int = 0,
var isActive: Boolean = false
)
fun main() {
// 使用 apply 進行對象創(chuàng)建和初始化
val user = User().apply {
username = "john_doe"
email = "john@example.com"
age = 27
isActive = false
println("Inside apply: Initialized user with username = $username, email = $email, age = $age, isActive = $isActive")
}
// 使用 also 記錄用戶創(chuàng)建后的調(diào)試信息,不改變對象
user.also {
println("Logging user creation: $it")
}
// 使用 let 檢查 email 是否為空并轉換處理,返回處理結果
val emailStatus = user.email.let { emailStr ->
if (emailStr.isNotEmpty()) {
"Email is valid: $emailStr"
} else {
"Email is empty"
}
}
println("Result from let: $emailStatus")
// 使用 run 進行用戶數(shù)據(jù)加工,返回格式化后的用戶描述信息
val userDescription = user.run {
// 進行一些邏輯處理,比如把用戶名大寫
username = username.uppercase()
isActive = age >= 18
"User: $username, Email: $email, Age: $age, Active: $isActive"
}
println("Formatted user description from run: $userDescription")
// 使用 with 來對用戶對象進行綜合操作,比如驗證并拼接信息
val registrationSummary = with(user) {
println("Inside with block: Verifying user details...")
// 假設這里進行一些復雜邏輯判斷
val status = if (isActive) "Active" else "Inactive"
"Registration Summary: Username = $username, Status = $status"
}
println("Summary from with: $registrationSummary")
}
在這個示例中,展示了每種擴展函數(shù)的具體應用:
使用 apply 完成對象的初始化和屬性設置,簡化重復引用。使用 also 記錄日志,但返回的是原始 user 對象,便于后續(xù)鏈式調(diào)用。使用 let 對對象的單一屬性(email)進行檢查,并返回一個字符串描述。使用 run 在對象上下文中進行邏輯處理,同時返回用戶描述信息。使用 with 封裝對 user 對象的綜合操作,并最后返回一個詳細的注冊摘要信息。
第九部分:深入討論各擴展函數(shù)的優(yōu)缺點
9.1 let 的優(yōu)缺點
優(yōu)點
簡單易用。let 能夠讓你清晰地以非空狀態(tài)使用對象并返回處理后的結果。理想的鏈式調(diào)用工具,可以將計算結果傳遞給下一個函數(shù)??梢詫⒖煽諏ο蟀踩幚怼⑥D換和后續(xù)邏輯局限在一個閉包內(nèi)。
缺點
如果濫用 let(比如在不需要轉換的時候),可能會使代碼閱讀性變差,過多嵌套的 let 可能會難以理解。
9.2 also 的優(yōu)缺點
優(yōu)點
非常適合用于執(zhí)行副作用(如調(diào)試或記錄日志),使得代碼更加整潔。不會改變對象,始終返回調(diào)用對象本身,利于鏈式調(diào)用。
缺點
僅用于副作用操作,如果主要邏輯依賴返回的額外信息,則不能使用也不適用。
9.3 apply 的優(yōu)缺點
優(yōu)點
簡潔地對對象進行初始化、設置屬性,使代碼整潔而直觀。代碼中可以直接引用對象成員,無需前綴,非常有利于對象構造。
缺點
總是返回對象本身,不適用于需要其他返回值的場景。當對象屬性較多或邏輯較復雜時,apply 代碼塊可能會變得冗長。
9.4 run 的優(yōu)缺點
優(yōu)點
靈活,既可以使用對象上下文進行計算,也可以作為非擴展函數(shù)直接執(zhí)行代碼塊??捎糜诜庋b一段局部邏輯,并返回計算結果,適用于臨時變量邏輯封裝。
缺點
與 apply 相比,代碼塊內(nèi)不能直接修改返回值為對象本身,而是返回 lambda 最后一行的值,一定程度上局限了鏈式操作。
9.5 with 的優(yōu)缺點
優(yōu)點
非擴展函數(shù)形式,使得函數(shù)調(diào)用更直觀,傳入對象后集中對對象的引用和操作。同樣適用于封裝對單個對象的多次操作,降低代碼重復調(diào)用對象名稱的復雜度。
缺點
與 run 無太大差異,但缺乏鏈式調(diào)用的流暢性,因為它不是擴展函數(shù)。
第十部分:實戰(zhàn)案例解析
為使擴展函數(shù)的用法更加貼近日常開發(fā),下面以一個實際的業(yè)務場景舉例,展示如何使用這些擴展函數(shù)來編寫清晰而高效的代碼。假設我們需要編寫一個圖片處理模塊,功能包括:
將輸入圖片進行非空判斷后轉換成灰度圖。在處理過程中記錄日志。對圖像對象進行一系列配置。最后返回一份處理報告。
下面是詳細的代碼示例和解釋:
package com.example.imageprocessor
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO
// 假設我們定義一個簡單的圖片處理函數(shù)
fun processImage(inputFile: File): String {
// 1. 使用 let 進行非空判斷和轉換:加載圖片文件
val image: BufferedImage? = inputFile.let {
println("Attempting to load image from ${it.absolutePath}")
// 如果文件存在則加載圖片,否則返回 null
if (it.exists()) ImageIO.read(it) else null
}
// 2. 使用 also 在加載后記錄日志并檢查狀態(tài)
image?.also {
println("Image successfully loaded: width = ${it.width}, height = ${it.height}")
}
// 3. 使用 apply 對圖片對象進行一系列配置(模仿配置操作)
val configuredImage = image?.apply {
// 模擬配置:例如調(diào)整圖像亮度,截取部分區(qū)域等
println("Applying configuration to the image")
// ... 這里實際會包含圖像處理邏輯
}
// 4. 使用 run 對處理結果作進一步計算,返回處理報告
val report = configuredImage?.run {
println("Processing image to generate report")
// 返回處理后圖片的基本信息字符串
"Processed image: width = $width, height = $height"
} ?: "Image processing failed. Image is null."
// 5. 使用 with 封裝一段多操作邏輯,比如生成額外的報告內(nèi)容
val finalReport = with(inputFile) {
"File: ${name}, Size: ${length()} bytes. Report: $report"
}
return finalReport
}
fun main() {
val imageFile = File("sample.jpg")
val processingReport = processImage(imageFile)
println("Final Report:\n$processingReport")
}
在這個案例中:
使用 let 對傳入的 File 對象進行處理,確保在讀取圖片前可以記錄日志及進行簡單判斷。also 用來做額外的日志記錄,不影響后續(xù)鏈式調(diào)用。apply 用于模擬對圖片對象的配置處理,直接設定處理邏輯。run 用于計算處理結果,并返回最終數(shù)據(jù);with 則整合文件信息和處理報告,生成綜合性的最終報告。
通過這樣的實戰(zhàn)案例,我們可以看到擴展函數(shù)如何協(xié)同工作,使得代碼邏輯清晰、結構分明,同時減少了重復的代碼和冗長的引用操作。
第十一部分:總結與歸納
經(jīng)過前面詳細的講解,下面將各擴展函數(shù)的核心要點進行再次歸納:
基本概念
let:以 it 作為參數(shù),返回 lambda 最后一行值,適合數(shù)據(jù)轉換、局部判斷。also:以 it 作為參數(shù),返回調(diào)用對象本身,適合插入副作用操作。apply:以 this 作為lambda 接收者,直接操作對象屬性,返回對象本身,適合對象初始化。run:以 this 作為lambda 接收者,返回 lambda 的最后表達式結果,既能操作對象又能返回數(shù)據(jù)。with:不是擴展函數(shù),而是普通函數(shù),傳入對象后封裝操作,返回計算結果。 參數(shù)區(qū)別
let、also 需要通過 it 引用對象;apply、run(擴展形式)和 with 則直接使用 this 進行操作,可以省略 this 關鍵字,調(diào)用對象隱式作為 lambda 的接收者。 使用場景
針對可空對象的判斷做 let;記錄日志和打印調(diào)試信息使用 also;對象初始化和構造使用 apply;對象內(nèi)操作計算返回特定數(shù)值使用 run;多次操作同一對象,并返回操作最終結果時使用 with。 實際應用 在日常開發(fā)中,根據(jù)場景需求選擇合適的擴展函數(shù),可以使代碼簡潔流暢,明確代碼執(zhí)行順序,減少冗余。同時,這些擴展函數(shù)可以一起使用,例如在一個業(yè)務流程中同時對對象進行配置、檢查和結果返回。
總體來說,Kotlin 的擴展函數(shù)是其語言設計中很強大的特性,通過理解每個擴展函數(shù)的作用和區(qū)別,開發(fā)者可以編寫出更簡潔、可讀性更強的代碼。同時,掌握這些擴展函數(shù)也有助于更好地理解 Kotlin 的編程范式與最佳實踐。
柚子快報邀請碼778899分享:76.Kotlin擴展函數(shù)簡介
本文內(nèi)容根據(jù)網(wǎng)絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉載請注明,如有侵權,聯(lián)系刪除。