Kotlin之邊學邊分析
什麼是委託模式?以及委託模式的原理和使用場景
koltin中聲明的成員屬性或者超類不在當前類中去實現,而是交給其他類去完成,叫做委託,使用by關鍵字來實現。
其使用場景適合那些需要複雜計算並且可以重複使用的場景
lazy懶惰性委託
lazy委託模式不會在聲明時就計算好值,而是在第一次使用時纔會計算值;並且以後也是用這個計算好的值;lazy(() -> t)參數是一個高階函數,其內部原理根據傳入參數分爲三種類型:
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
SYNCHRONIZED:
默認參數類型,lazy計算時會加鎖進入線程同步模式,性能可能會下降
PUBLICATION:
多線程安全模式,內部採用自旋鎖CAS方式比較判斷寫入值,性能比同步好點
NONE:
不會加任何索,多線程不安全,適用於單線程操作
自定義委託模式類
屬性委託關鍵在於提供get方法,kotlin提供超類ReadOnlyProperty 或 ReadWriteProperty兩個接口,根據使用場景繼承他們,重寫get和set(var型變量)方法即可;在Android中我們可以的SharedPreference可以使用屬性委託來很好的爲我們工作
類委託(代理模式)
上面講的都是屬性委託,而類的委託則適用於另一個場景,必須委託的基類是接口;其實質就是一種代理模式,類委託基本結構分三類:基礎接口類Base、已實現Base的類Base1,以及我們需要代理的類Base2,如下:
interface Base{
fun print()
}
class Base1 : Base{
fun print(){
System.out.println("----")
}
}
class Base2(b : Base) : Base by b{
}
以上Base2傳入參數b,以及後面委託b,實質就是Base2靜態代理b對象,無須本身去實現,kotlin幫你去生成中間代碼,反編譯情況確實如此:
更多相關知識請點擊https://blog.csdn.net/zxc123e/article/details/73836013
Kotlin語法糖apply
如題,apply關鍵字作爲語法糖,有着提高開發效率,優化代碼結構的好處!但是我認爲也有不好的地方,請看下面的分析
基本使用
class AFragment : Fragment() {
private val data : Bundle = Bundle()
private fun test(){
LogHelper.de_i("--------")
}
companion object {
AFragment().apply {
內部操作相當於在AFragment環境下操作,
可以執行AFragment的函數和成員;
私有成員也可以調用;
test()
data.putString("a", "a")
}
}
}
使用場景: 可以用到一些實例初始化,需要很多參數初始化那種,不需要寫以前那種建造者模式了,方便得很
原理分析
看看java層源碼對Apply這個語法糖是如何定義:
inline內聯函數,傳入block高階函數,而且是在T模板下的高階函數,所以可以
執行T模板類下的函數
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
執行高階函數
block()
return this
}
看到上面大致明白了,apply傳入的T類型下的高階函數,所以可以執行T的函數;但是T類型的私有函數僅僅憑上面的操作是辦不到的,因爲私有成員只能自己類裏面調用,外部是無法調用的,如何破?
kotlin採用編譯時,根據部分註解,爲這些私有成員生成一些公有靜態方法,其參數傳入T類,在靜態方法內部調用這些私有成員,反編譯上面的基本使用的樣例代碼,看看生成了哪些
a. 先看看AFragment.smali類,省去部分代碼保留關鍵代碼
# static fields
這是我能定義的伴生對象Companion Object,自動生成一個靜態對象,在類加載器加載時創建
.field public static final Companion:Lcom/jz/appframe/AFragment$Companion;
創建的私有成員data
.field private final data:Landroid/os/Bundle;
# direct methods
cinit爲類加載器加載時執行的構造方法,而init是創建對象時執行
.method static constructor <clinit>()V
.locals 2
創建AFragment$Companion對象,保存到v0寄存器
new-instance v0, Lcom/jz/appframe/AFragment$Companion;
const/4 v1, 0x0
初始化AFragment$Companion對象
invoke-direct {v0, v1}, Lcom/jz/appframe/AFragment$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
把v0寄存器內容賦值給成員Companion:Lcom
sput-object v0, Lcom/jz/appframe/AFragment;->Companion:Lcom/jz/appframe/AFragment$Companion;
return-void
.end method
私有函數
.method private final test()V
.locals 1
.line 36
const-string v0, "--------"
invoke-static {v0}, Lcom/jz/appframe/util/LogHelper;->de_i(Ljava/lang/String;)V
.line 37
return-void
.end method
共有靜態方法,對應上面的私有test方法
.method public static final synthetic access$test(Lcom/jz/appframe/AFragment;)V
.locals 0
.param p0, "$this" # Lcom/jz/appframe/AFragment;
.line 21
調用test方法,傳入p0是一個AFragment對象
invoke-direct {p0}, Lcom/jz/appframe/AFragment;->test()V
return-void
.end method
共有靜態方法,對應上面的私有成員
.method public static final synthetic access$getData$p(Lcom/jz/appframe/AFragment;)Landroid/os/Bundle;
.locals 1
.param p0, "$this" # Lcom/jz/appframe/AFragment;
.line 21
同上test理解
iget-object v0, p0, Lcom/jz/appframe/AFragment;->data:Landroid/os/Bundle;
return-object v0
.end method
這是針對伴生對象Companion對象方法生成靜態方法,所有可以直接使用外部類調用newInstance方法,
實質調用的是這個方法,然後內部在使用半生對象實例調用newInsatnce
.method public static final newInstance(Ljava/lang/String;Ljava/lang/String;)Lcom/jz/appframe/AFragment;
.locals 1
.annotation runtime Lkotlin/jvm/JvmStatic;
.end annotation
get拿到伴生對象Companion
sget-object v0, Lcom/jz/appframe/AFragment;->Companion:Lcom/jz/appframe/AFragment$Companion;
調用Companion的newInstance方法
invoke-virtual {v0, p0, p1}, Lcom/jz/appframe/AFragment$Companion;->newInstance(Ljava/lang/String;Ljava/lang/String;)Lcom/jz/appframe/AFragment;
move-result-object v0
return-object v0
.end method
上面AFragment除了我們定義的私有成員之外,還生成了許多輔助的公有靜態方法,目的是爲了可以從外部調用AFragment的私有成員;看看半生對象做了啥操作,主要看newInstance:
.method public final newInstance(Ljava/lang/String;Ljava/lang/String;)Lcom/jz/appframe/AFragment;
.locals 8
p1和p2是參數
.param p1, "param1" # Ljava/lang/String;
.param p2, "param2" # Ljava/lang/String;
.annotation runtime Lkotlin/jvm/JvmStatic;
.end annotation
const-string v0, "param1"
檢查參數是否爲空
invoke-static {p1, v0}, Lkotlin/jvm/internal/Intrinsics;->checkParameterIsNotNull(Ljava/lang/Object;Ljava/lang/String;)V
const-string v1, "param2"
invoke-static {p2, v1}, Lkotlin/jvm/internal/Intrinsics;->checkParameterIsNotNull(Ljava/lang/Object;Ljava/lang/String;)V
.line 57
創建AFragment對象
new-instance v2, Lcom/jz/appframe/AFragment;
執行構造方法
invoke-direct {v2}, Lcom/jz/appframe/AFragment;-><init>()V
把AFragment對象同時給了v3和v2,爲什麼要這麼做,還不是apply語法糖;
相當於兩個指針同時存儲一個對象,一個指針用於apply內部的高階函數操作;
另一個保存這個初始指針使用,下面的代碼也是這麼做的
move-object v3, v2
這個怪?竟然還有這種用法,第一次遇到,不難理解,這就是對v3這個本地寄存器
解釋,v3類型是AFragment,名字是$this$apply;v3作爲apply內部指針使用
v2作爲返回對象使用
.local v3, "$this$apply":Lcom/jz/appframe/AFragment;
const/4 v4, 0x0
.line 58
.local v4, "$i$a$-apply-AFragment$Companion$newInstance$1":I
創建一個Bundle
new-instance v5, Landroid/os/Bundle;
invoke-direct {v5}, Landroid/os/Bundle;-><init>()V
move-object v6, v5
.local v6, "$this$apply":Landroid/os/Bundle;
const/4 v7, 0x0
.line 59
.local v7, "$i$a$-apply-AFragment$Companion$newInstance$1$1":I
invoke-virtual {v6, v0, p1}, Landroid/os/Bundle;->putString(Ljava/lang/String;Ljava/lang/String;)V
.line 60
invoke-virtual {v6, v1, p2}, Landroid/os/Bundle;->putString(Ljava/lang/String;Ljava/lang/String;)V
.line 61
v6寄存器結束使用了
.end local v6 # "$this$apply":Landroid/os/Bundle;
.end local v7 # "$i$a$-apply-AFragment$Companion$newInstance$1$1":I
nop
.line 58
v5設置到AFragment的bundle上
invoke-virtual {v3, v5}, Lcom/jz/appframe/AFragment;->setArguments(Landroid/os/Bundle;)V
.line 62
調用了AFragment的共有靜態方法去設置私有成員,access$test是AFragment中自動生成的
invoke-static {v3}, Lcom/jz/appframe/AFragment;->access$test(Lcom/jz/appframe/AFragment;)V
.line 63
data私有成員也是一樣的,調用公有靜態方法去設置
invoke-static {v3}, Lcom/jz/appframe/AFragment;->access$getData$p(Lcom/jz/appframe/AFragment;)Landroid/os/Bundle;
move-result-object v0
const-string v1, "a"
invoke-virtual {v0, v1, v1}, Landroid/os/Bundle;->putString(Ljava/lang/String;Ljava/lang/String;)V
.line 64
寄存器結束
.end local v3 # "$this$apply":Lcom/jz/appframe/AFragment;
.end local v4 # "$i$a$-apply-AFragment$Companion$newInstance$1":I
nop
.line 57
nop
.line 64
return-object v2
.end method
總結
apply確實可以提高手動編寫代碼的開發效率,爲了完成私有成員調用,編譯時生成了許多公有靜態方法來調用!同時apply在完成高階函數執行操作時,每次都多創建一個寄存器,如上move-object v6, v5;這樣看來apply爲了完成高效的代碼操作也付出了很多東西;一個靜態代碼區新增了方法,二是運行時,寄存器會比平時多一倍,所以在內存上還是有開銷存在的,並且調用邏輯是多了幾個步驟的,AFragment.newInstance創建AFragment變量,調用邏輯依次是從AFragment.newInstance -> AFragment$Companion.newInstance ->AFragment.access$test -> access$getData$p,完成了AFragment的創建和初始化了;所以使用apply模式使用要注意了
Kotlin語法糖let
與apply不同,let則是將調用者當做參數傳遞進入高階函數,並且將高階函數最後的返回值返回,當然,如果高階函數沒有返回值也沒關係!看看他的定義就知道了:
內斂函數
block高階函數,T是入參 R是回參
public inline fun <T, R> T.let(block: (T) -> R): R {
contract是契約,告知編譯器在合適的地方調用,也就是外層函數執行時去調用
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
直接返回
return block(this)
}
逆向let爲smali
對這段kotlin逆向的結果查看
源碼 arguments爲bundle
arguments?.let {
param1 = it.getString(ARG_PARAM1)
}
.line 29
獲取arguments
invoke-virtual {p0}, Lcom/jz/appframe/AFragment;->getArguments()Landroid/os/Bundle;
move-result-object v0
檢查arguments等於0不
if-eqz v0, :cond_0
解釋v0的含義,名字爲it,類型爲bundle
.local v0, "it":Landroid/os/Bundle;
const/4 v1, 0x0
.line 30
這個v1寄存器不知道是幹啥用的,只標記了一下,根本沒用上
.local v1, "$i$a$-let-AFragment$onCreate$1":I
const-string v2, "param1"
invoke-virtual {v0, v2}, Landroid/os/Bundle;->getString(Ljava/lang/String;)Ljava/lang/String;
move-result-object v2
.line 32
.end local v0 # "it":Landroid/os/Bundle;
.end local v1 # "$i$a$-let-AFragment$onCreate$1":I
如上,let也沒有什麼神奇的,常規操作,判空arguments後,直接用他的寄存器進行操作;可以適用於一些判空後要對調用者進行配置的場景,不過裏面會多一個整型類型的寄存器,也沒用到,不知道爲何,有知道的請留言交流交流,上面的apply也是