網站首頁 編程語言 正文
平時看博客或者學知識,學到的東西比較零散,沒有獨立的知識模塊概念,而且學了之后很容易忘。于是我建立了一個自己的筆記倉庫 (一個我長期維護的筆記倉庫,感興趣的可以點個star~你的star是我寫作的巨大大大大的動力),將平時學到的東西都歸類然后放里面,需要的時候呢也方便復習。
1.前置知識
在Kotlin中,函數是一等公民,它也是有自己的類型的。比如()->Unit
,函數類型是可以被存儲在變量中的。
Kotlin中的函數類型形如:()->Unit
、(Int,Int)->String
、Int.(String)->String
等。它們有參數和返回值。
最后一個Int.(String)->String
比較奇怪,它表示函數類型可以有一個額外的接收者類型。這里表示可以在Int對象上調用一個String類型參數并返回一個String類型的函數。
val test: Int.(String) -> String = { param -> "$this param=$param" } println(1.test("2")) println(test(1, "2"))
如果我們把Int.(String) -> String
類型定義成變量,并給它賦值,后面的Lambda的參數param就是傳入的String類型,最后返回值也是String,而在這個Lambda中用this表示前面的接收者類型Int的對象,有點像擴展函數,可以在函數內部通過this來訪問一些成員變量、成員方法什么的。可以把這種帶接收者的函數類型,看成是成員方法。
因為它的聲明方式有點像擴展函數,所以我們可以使用1.test("2")
來調用test這個函數類型,它其實編譯之后最終是將1這個Int作為參數傳進去的。所以后面的test(1, "2")
這種調用方式也是OK的。
有了上面的知識補充,咱們再來看Kotlin的標準庫函數apply
public inline fun <T> T.apply(block: T.() -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() return this }
- 首先apply是一個擴展函數,其次是帶泛型的,意味著任何對象都可以調用apply函數。
- 接著它的參數是帶接收者的函數類型,接收者是T,那么調用block()就像是調用了T對象里面的一個成員函數一樣,在block函數內部可以使用this來對公開的成員變量和公開的成員函數進行訪問
- 返回值:就是T,哪個對象調用的該擴展函數就返回哪個對象
2.使用
作用域函數是Kotlin內置的,可對數據進行操作轉換等。
先來看個demo,let和run
data class User(val name: String) fun main() { val user = User("云天明") val letResult = user.let { param -> "let 輸出點東西 ${param.name}" } println(letResult) val runResult = user.run { //this:User "run 輸出點東西 ${this.name}" } println(runResult) }
let和run是類似的,都會返回Lambda的執行結果,區別在于let有Lambda參數,而run沒有。但run可以使用this來訪問user對象里面的公開屬性和函數。
also和apply也是類似的
user.also { param-> println("also ${param.name}") }.apply { //this:User println("apply ${this.name}") }
also和apply返回的是當前執行的對象,also有Lambda參數(這里的Lambda參數就是當前執行的對象),而apply沒有Lambda參數(而是通過this來訪問當前執行的對象)。
repeat是重復執行當前Lambda
repeat(5) { println(user.name) }
with比較特殊,它不是以擴展方法的形式存在的,而是一個頂級函數
with(user) { //this: User println("with ${this.name}") }
with的Lambda內部沒有參數,而是可以通過this來訪問傳入對象的公開屬性和函數。
3.源碼賞析
使用這塊的話,不多說,想必大家已經非常熟悉,我們直接開始源碼賞析。
3.1 let和run
//let和run是類似的,都會返回Lambda的執行結果,區別在于let有Lambda參數,而run沒有。但run可以使用this來訪問user對象里面的公開屬性和函數。 public inline fun <T, R> T.let(block: (T) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block(this) } public inline fun <T, R> T.run(block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() }
- let和run都是擴展函數
- let的Lambda有參數,該參數就是T,也就是待擴展的那個對象,所以可以在Lambda內訪問該參數,從而訪問該參數對象的內部公開屬性和函數
- run的Lambda沒有參數,但這個Lambda是待擴展的那個對象T的擴展,這是帶接收者的函數類型,所以可以看做這個Lambda是T的成員函數,直接調用該Lambda就是相當于直接調用該T對象的成員函數,所以在該Lambda內部可以通過this來訪問T的公開屬性和函數(只能訪問公開的,稍后解釋是為什么)。
- let和run都是返回的Lambda的執行結果
3.2 also和apply
//also和apply都是返回原對象本身,區別是apply沒有Lambda參數,而also有 public inline fun <T> T.also(block: (T) -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block(this) return this } public inline fun <T> T.apply(block: T.() -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() return this }
- also和apply都是擴展函數
- also和apply都是返回原對象本身,區別是apply沒有Lambda參數,而also有
- also的Lambda有參數,該參數就是T,也就是待擴展的那個對象,所以可以在Lambda內訪問該參數,從而訪問該參數對象的內部公開屬性和函數
- apply的Lambda沒有參數,但這個Lambda是待擴展的那個對象T的擴展,這是帶接收者的函數類型,所以可以看做這個Lambda是T的成員函數,直接調用該Lambda就是相當于直接調用該T對象的成員函數,所以在該Lambda內部可以通過this來訪問T的公開屬性和函數(只能訪問公開的,稍后解釋是為什么)。
3.3 repeat
public inline fun repeat(times: Int, action: (Int) -> Unit) { contract { callsInPlace(action) } for (index in 0 until times) { action(index) } }
- repeat是一個頂層函數
- 該函數有2個參數,一個是重復次數,另一個是需執行的Lambda,Lambda帶參數,該參數表示第幾次執行
- 函數內部非常簡單,就是一個for循環,執行Lambda
3.4 with
public inline fun <T, R> with(receiver: T, block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return receiver.block() }
- with是一個頂層函數
- with有2個參數,一個是接收者,一個是帶接收者的函數
- with的返回值就是block函數的返回值
- block是T的擴展,所以可以使用receiver對象直接調用block函數,而且block內部可以使用this來訪問T的公開屬性和函數
4.反編譯
了解一下這些作用域函數編譯之后到底長什么樣子,先看下demo
data class User(val name: String) fun main() { val user = User("云天明") val letResult = user.let { param -> "let 輸出點東西 ${param.name}" } println(letResult) val runResult = user.run { //this:User "run 輸出點東西 ${this.name}" } println(runResult) user.also { param -> println("also ${param.name}") }.apply { //this:User println("apply ${this.name}") } repeat(5) { println(user.name) } val withResult = with(user) { //this: User println("with ${this.name}") "with 輸出點東西 ${this.name}" } println(withResult) }
然后反編譯看一下,data class的反編譯咱就不看了,只關注main內部的代碼
User user = new User("云天明"); System.out.println("let 輸出點東西 " + user.getName()); System.out.println("run 輸出點東西 " + user.getName()); User $this$test_u24lambda_u2d3 = user; System.out.println("also " + $this$test_u24lambda_u2d3.getName()); System.out.println("apply " + $this$test_u24lambda_u2d3.getName()); for (int i = 0; i < 5; i++) { int i2 = i; System.out.println(user.getName()); } User $this$test_u24lambda_u2d5 = user; System.out.println("with " + $this$test_u24lambda_u2d5.getName()); System.out.println("with 輸出點東西 " + $this$test_u24lambda_u2d5.getName());
可以看到,let、run、also、apply、repeat、with的Lambda內部執行的東西,全部放外面來了(因為inline),不用把Lambda轉換成Function(匿名內部類啥的),這樣執行起來性能會高很多。
額…我其實還想看一下block: T.() -> R
這種編譯出來是什么樣子的,上面的那些作用域函數全部是inline的函數,看不出來了。我自己寫一個看一下,自己寫幾個類似let、run、with的函數,但不帶inline:
public fun <T, R> T.letMy(block: (T) -> R): R { return block(this) } public fun <T, R> T.runMy(block: T.() -> R): R { return block() } public fun <T, R> withMy(receiver: T, block: T.() -> R): R { return receiver.block() } fun test() { val user = User("云天明") val letResult = user.letMy { param -> "let 輸出點東西 ${param.name}" } println(letResult) val runResult = user.runMy { //this:User "run 輸出點東西 ${this.name}" } println(runResult) val withResult = withMy(user) { //this: User println("with ${this.name}") "with 輸出點東西 ${this.name}" } println(withResult) }
反編譯出來的樣子:
final class TestKt$test$letResult$1 extends Lambda implements Function1<User, String> { public static final TestKt$test$letResult$1 INSTANCE = new TestKt$test$letResult$1(); TestKt$test$letResult$1() { super(1); } public final String invoke(User param) { Intrinsics.checkNotNullParameter(param, "param"); return "let 輸出點東西 " + param.getName(); } } final class TestKt$test$runResult$1 extends Lambda implements Function1<User, String> { public static final TestKt$test$runResult$1 INSTANCE = new TestKt$test$runResult$1(); TestKt$test$runResult$1() { super(1); } public final String invoke(User $this$runMy) { Intrinsics.checkNotNullParameter($this$runMy, "$this$runMy"); return "run 輸出點東西 " + $this$runMy.getName(); } } final class TestKt$test$withResult$1 extends Lambda implements Function1<User, String> { public static final TestKt$test$withResult$1 INSTANCE = new TestKt$test$withResult$1(); TestKt$test$withResult$1() { super(1); } public final String invoke(User $this$withMy) { Intrinsics.checkNotNullParameter($this$withMy, "$this$withMy"); System.out.println("with " + $this$withMy.getName()); return "with 輸出點東西 " + $this$withMy.getName(); } } public final class TestKt { public static final <T, R> R letMy(T $this$letMy, Function1<? super T, ? extends R> block) { Intrinsics.checkNotNullParameter(block, "block"); return block.invoke($this$letMy); } public static final <T, R> R runMy(T $this$runMy, Function1<? super T, ? extends R> block) { Intrinsics.checkNotNullParameter(block, "block"); return block.invoke($this$runMy); } public static final <T, R> R withMy(T receiver, Function1<? super T, ? extends R> block) { Intrinsics.checkNotNullParameter(block, "block"); return block.invoke(receiver); } public static final void test() { User user = new User("云天明"); System.out.println((String) letMy(user, TestKt$test$letResult$1.INSTANCE)); System.out.println((String) runMy(user, TestKt$test$runResult$1.INSTANCE)); System.out.println((String) withMy(user, TestKt$test$withResult$1.INSTANCE)); } }
在我寫的demo中letMy、runMy、withMy的Lambda都被編譯成了匿名內部類,它們都繼承自kotlin.jvm.internal.Lambda
這個類,且都實現了Function1<User, String>
接口。
abstract class Lambda<out R>(override val arity: Int) : FunctionBase<R>, Serializable { override fun toString(): String = Reflection.renderLambdaToString(this) } interface FunctionBase<out R> : Function<R> { val arity: Int } public interface Function<out R> public interface Function1<in P1, out R> : Function<R> { public operator fun invoke(p1: P1): R }
這里的Lambda是一個Kotlin內置的一個類,它就是一個Function,用來表示函數類型的值。而Function1則是繼承自Function,它表示有一個參數的函數類型。除了Function1,Kotlin還內置了Function2、Function3、Function4等等,分別代表了2、3、4個參數的函數類型。就是這么簡單粗暴。
回到上面的反編譯代碼中,我們發現letMy函數,傳入user對象和TestKt$test$letResult$1.INSTANCE
這個單例對象,并且在執行的時候,是用單例對象調用invoke函數,然后將user傳進去的。在TestKt$test$letResult$1#invoke
中,接收到了user對象,然后通過該對象訪問其函數。可以看到,這里是用user對象去訪問對象中的屬性或者函數,那么肯定是只能訪問到公開的屬性和函數,這也就解答了上面的疑惑。
其他2個,runMy和withMy函數,竟然在編譯之后和letMy長得一模一樣。這意味著block: (T) -> R
和block: T.() -> R
是類似的,編譯之后代碼一模一樣。都是將T對象傳入invoke函數,然后在invoke函數內部進行操作T對象。
5.小結
Kotlin作用域函數在日常編碼中,使用頻率極高,所以我們需要簡單了解其基本原理,萬一出了什么事方便找問題。理解作用域函數,得先理解函數類型,在Kotlin中函數也是有類型的,形如:()->Unit
、(Int,Int)->String
、Int.(String)->String
等,它們可以被存儲與變量中。let、run、apply、also都是擴展函數,with、repeat是頂層函數,它們都是inline修飾的函數,編譯之后Lambda就沒了,直接把Lambda內部的代碼搬到了外邊,提高了性能。
感謝大家的觀看,希望本文能幫助大家更深地理解作用域函數。
課后小練習:
如果你覺得自己完全理解了本文,不妨拿出文本編輯器,把let、run、apply、also、with、repeat默寫出來,可能會有更深地理解效果。
原文鏈接:https://blog.csdn.net/xfhy_/article/details/126458540
相關推薦
- 2022-12-31 Kotlin?Jetpack組件ViewModel使用詳解_Android
- 2022-07-07 C語言數組快速入門詳細講解_C 語言
- 2024-03-21 Nacos簡介
- 2022-12-05 C?sharp?(#)?數據類型獲取方式_C#教程
- 2023-01-29 python缺失值填充方法示例代碼_python
- 2022-11-13 Python?pandas按行、按列遍歷DataFrame的幾種方式_python
- 2022-06-12 postgreSQL數據庫基本概念教程_PostgreSQL
- 2022-10-12 C#設計模式之建造者模式生成器模式示例詳解_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支