本文收錄于 kotlin入門潛修專題系列镣衡,歡迎學(xué)習(xí)交流。
創(chuàng)作不易档悠,如有轉(zhuǎn)載廊鸥,還請備注。
寫在前面
人一能之辖所,己百之惰说;人十能之,己千之缘回。果能此道吆视,雖愚必明,雖柔必強(qiáng)酥宴±舶桑——與君共勉。
object表達(dá)式及聲明
在kotlin入門潛修之類和對象篇—嵌套類及其原理這篇文章中我們已經(jīng)使用過了object關(guān)鍵字拙寡,從文中可知授滓,使用object能夠在kotlin中實現(xiàn)類似于java中的匿名內(nèi)部類的功能。但由于那篇文章主要在是闡述內(nèi)部類肆糕,因此沒有過多篇幅對object做較深闡述般堆,而本篇文章就是要對object進(jìn)行一次深入探討。
再次強(qiáng)調(diào)诚啃,此object不是java中的Object類郁妈,而是kotlin中的關(guān)鍵字,該object首字母是小寫的绍申。
kotlin提供object關(guān)鍵字的用意噩咪,就是在生成一個對當(dāng)前類進(jìn)行輕微修改的類對象顾彰、且不需要聲明一個新的子類的時候使用。本篇文章將從object作為表達(dá)式以及object作為聲明兩部分來闡述下kotlin中的object的用法胃碾。
object表達(dá)式
先來回顧下匿名內(nèi)部類的實現(xiàn)涨享,示例如下:
//MyListener接口,用于監(jiān)聽點擊事件
interface MyListener {
fun onClick()//這里是當(dāng)點擊發(fā)生時的回調(diào)方法
}
//我們暴露了一個設(shè)置點擊監(jiān)聽器的入口
fun setOnCliCkListener(listener: MyListener) {
//假設(shè)當(dāng)點擊事件發(fā)生時仆百,在這里通過listener通知外界
//也即通過調(diào)用listener.onClick方法實現(xiàn)
}
//測試類
class Main {
companion object {
@JvmStatic
fun main(args: Array<String>) {
//注意這里厕隧,通過匿名類對象實現(xiàn)了onClick方法
setOnCliCkListener(object : MyListener {
override fun onClick() {
println("clicked...")
}
})
}
}
}
上面的代碼是ui交互中典型的監(jiān)聽事件的寫法,也可被稱為觀察者模式俄周,很好的體現(xiàn)了匿名類的用處吁讨。但重點想表明的是,這段代碼中object實際上是作為表達(dá)式存在的峦朗。為什么這么說建丧?這是因為上述代碼實際上是產(chǎn)生了object類型的匿名對象,這是表達(dá)式和聲明的本質(zhì)區(qū)別:作為表達(dá)式時可以有右值波势,反之不行翎朱。
再來看幾個object作為表達(dá)式的幾個例子。
interface MyListener {
fun onClick()
}
open class Test(val name: String) {}
//object作為表達(dá)式尺铣,返回了匿名對象
val obj: MyListener = object : Test("test"), MyListener {
override fun onClick() {
print(name)
}
}
上面代碼需要注意以下幾點:
- 如果父類構(gòu)造方法有入?yún)⑺┣瑒t在生成匿名對象時必須要按父類構(gòu)造方法的定義傳參。
- 由于生成的匿名對象有多個父實現(xiàn)凛忿,所以在聲明obj時必須指定類型澈灼,即val obj: MyListener后面的MyListener必須要顯示指定。
當(dāng)object作為表達(dá)式時店溢,還可以單純的作為一個對象存在蕉汪,這個時候沒有任何超類型。如下所示:
fun test() {
val add = object {//沒有任何超類逞怨,只是作為一個對象存在
var x: Int = 1
var y: Int = 2
}
println(add.x + add.y)//打印 '3'
}
有朋友會發(fā)現(xiàn),上面我們演示的匿名object的用法基本都是在本地(區(qū)別于類成員)定義的福澡,那么匿名object能否作為屬性存在呢叠赦?答案是可以的,但是匿名object作為屬性存在時有一些限制:
- 匿名object作為private屬性時革砸,其表達(dá)式返回的類型就是object類型
- 匿名object作為public屬性時除秀,其表達(dá)式返回的類型將是其超類的類型,如果沒有超類算利,則返回kotlin中的頂級類Any類型册踩。
看個例子就會明白:
class Test() {
private val test1 = object {//注意這里test1被聲明了private
val value = "test1"
}
public val test2 = object {//注意這里test2被聲明了public(其實可以省略,因為默認(rèn)就是public)
val value = "test2"
}
fun test() {
val val1 = test1.value//正確效拭,private修飾的匿名object返回類型就是object類型
val val2 = test2.value//錯誤暂吉,public修飾的匿名object返回的類型是其超類
//這里的超類就是Any,而Any類并沒有value字段
}
}
同java一樣胖秒,kotlin允許在匿名類對象內(nèi)部訪問外部的成員,但是有一點與java不一樣慕的,那就是此時外部成員不必再聲明為final(java中關(guān)鍵字阎肝,表示不可變的,對應(yīng)于val)肮街,示例如下:
class Main {
var className = "Main.class"
fun test() {
setOnCliCkListener(object : MyListener {
override fun onClick() {
println(className)//這里使用外部的className屬性风题,此時外部也不必聲明為不可變的
}
})
}
}
object聲明
上一章節(jié)講述了object作為表達(dá)式的一些用法,本章節(jié)講述object作為聲明時的一些用法嫉父。
當(dāng)object作為聲明存在時沛硅,其實就是我們前面文章中已經(jīng)闡述過的kotlin中單例的寫法。在kotlin中绕辖,可以不用再像java那樣自己去實現(xiàn)單例摇肌,而是通過提供關(guān)鍵字來保證單例,這個關(guān)鍵字就是object引镊。當(dāng)object作為聲明修飾一個“class”時朦蕴,這個“class”就只有一個對象。示例如下:
object SingleInstance {//使用object來聲明一個單例對象
}
class Main {
companion object {
@JvmStatic
fun main(args: Array<String>) {
val s1 = SingleInstance//注意這里不再是SingleInstance()
val s2 = SingleInstance
println(s1 === s2)//打印'true'
}
}
}
上面代碼中s1===s2打印結(jié)果為true標(biāo)明了SingleInstance就是個單例對象弟头。
那么如何使用單例對象呢吩抓?很簡單,像普通對象一樣使用即可:
object SingleInstance {//單例
fun test(){}//有個test方法
}
//測試方法test
fun test(){
SingleInstance.test()//直接通過單例名來調(diào)用其test方法
}
object作為聲明時需要注意以下幾點:
- object作為聲明時是沒有右值的赴恨,所以無法像上一章節(jié)中作為表達(dá)式那樣賦值給其他變量疹娶。
- object作為聲明時無法聲明本地變量,但可以作為類成員存在伦连。
- object作為聲明存在時雨饺,可以為其定義超類,也就是說單例可以有超類惑淳,如下所示:
object SingleInstance:MyListener {//該單例實現(xiàn)了MyListener接口
override fun onClick() {
}
}
最后需要說明的是额港,kotlin中的單例天生是線程安全的,所以不必像java那樣考慮多線程情況歧焦。
伴隨對象(Companion Objects)
在闡述伴隨對象之前先來看個伴隨對象的使用例子:
class MyClass {
companion object {//這就是伴隨對象的定義
fun test() {}
}
}
//測試類
class Main {
companion object {//實際上我們已經(jīng)用多很多次了
@JvmStatic
fun main(args: Array<String>) {
MyClass.test()//伴隨對象的調(diào)用
MyClass.Companion.test()//你也可以通過這種方式調(diào)用
}
}
}
上面代碼展示了伴隨對象的定義及其使用移斩,事實上,我們已經(jīng)多次見識過伴隨對象了:那就是每次測試時候使用的main方法绢馍。
通過上面代碼可以知道向瓷,伴隨對象可以直接通過類名來進(jìn)行訪問,也可以通過kotlin為我們提供的Companion成員來調(diào)用舰涌。其實還可以通過下面方式來調(diào)用:
class MyClass {
companion object {
fun test() {}
}
}
//測試方法
fun m1(){
val obj = MyClass//竟然可以直接通過類名進(jìn)行賦值!!!
obj.test()//調(diào)用伴隨對象的test方法
}
很神奇猖任,竟然可以通過類名直接調(diào)用伴隨對象中的test方法,這些原理我們稍后再來討論瓷耙。先看看kotlin對此用法的說明:
無論伴隨對象有沒有命名朱躺,只要使用包含有伴隨對象的類名進(jìn)行賦值的時候刁赖,此值實際上就是該類所持有的其內(nèi)部伴隨對象的引用地址。
那么如果一個類中含有多個伴隨對象會怎樣呢室琢?很抱歉乾闰,kotlin規(guī)定一個類中只允許存在一個伴隨對象!
kotlin中的伴隨對象使用起來雖然很像java中static修飾的類成員的使用方法盈滴,但實際上它依然是以對象的形式調(diào)用的涯肩,和static修飾的成員是不一樣的。
kotlin中的伴隨對象是可以繼承類或?qū)崿F(xiàn)接口的巢钓,如下所示:
interface MyListener {//定義了一個MyListener接口
fun onClick()
}
open class Test{}//定義了一個類Test
class MyClass {
companion object : Test(),MyListener {//伴隨對象繼承了Test類病苗,并且實現(xiàn)了MyListener接口
override fun onClick() {
}
}
}
但是如果我們使用@JvmStatic注解修飾就表示和java中的static成員是一樣。比如我們常用的main方法中你會發(fā)現(xiàn)都是用了@JvmStatic注解修飾症汹,這是因為硫朦,java的入口方法main必須是static的,而伴隨對象中的成員并不是static的背镇,所以需要額外加上static修飾符告知編譯器咬展,這個是真正意義上的static方法。
那么伴隨對象的初始化時機(jī)是什么瞒斩?是和普通成員一致還是和靜態(tài)成員一致破婆?
事實上,伴隨對象是在其所屬類加載的時候完成初始化的胸囱,是和java中的靜態(tài)成員初始化時機(jī)一致的祷舀。
object的原理
又到了刨根問底的時候了,本篇章節(jié)會闡述object實現(xiàn)的底層原理烹笔。
照例裳扯,先上一段要分析的object的源代碼,如下所示:
object SingleInstance {//使用object來聲明一個單例對象
}
沒錯谤职,就是這么一個非常簡單的object聲明源碼饰豺,我來看下其生成的字節(jié)碼到底是什么,如下所示:
public final class SingleInstance {//字節(jié)碼對應(yīng)的SingleInstance類
// access flags 0x2
private <init>()V//注意允蜈,構(gòu)造方法是私有的
L0
LINENUMBER 1 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
ALOAD 0
CHECKCAST SingleInstance
PUTSTATIC SingleInstance.INSTANCE : LSingleInstance;
RETURN
L1
LOCALVARIABLE this LSingleInstance; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x19
public final static LSingleInstance; INSTANCE//這里生成了一個靜態(tài)的常量實例
// access flags 0x8
static <clinit>()V//類構(gòu)造方法初始化
L0
LINENUMBER 1 L0
NEW SingleInstance
INVOKESPECIAL SingleInstance.<init> ()V//在類構(gòu)造方法初始化的時候調(diào)用了其實例構(gòu)造方法
RETURN
MAXSTACK = 1
MAXLOCALS = 0
// compiled from: Main.kt
}
通過上面字節(jié)碼我們可以總結(jié)以下幾點:
- 使用object修飾后之所以是單例的冤吨,是因為其構(gòu)造方法是private的,我們無法在外部進(jìn)行對象生成陷寝,其對應(yīng)的字節(jié)碼摘錄如下:
private <init>()V//私有的構(gòu)造方法
- object修飾的單例是線程安全的,這是因為在其類構(gòu)造方法初始化的時候就完成了實例的生成其馏,這個是由類加載器來保證的凤跑。字節(jié)碼摘錄如下:
static <clinit>()V//類構(gòu)造初始化方法
L0
LINENUMBER 1 L0
NEW SingleInstance///此處及下面代碼生成了唯一的一個SingleInstance實例
INVOKESPECIAL SingleInstance.<init> ()V
RETURN
MAXSTACK = 1
MAXLOCALS = 0
那么我們是怎么用調(diào)用該唯一的一個實例呢?很簡單溅潜,我們增加一個測試方法蹦误,看下其生成的字節(jié)碼即可,如下所示:
fun test(){
SingleInstance//這種寫法是允許的椎工,我們只是想看看其對應(yīng)該的字節(jié)碼而已
}
其對應(yīng)的字節(jié)碼如下所示:
public final static test()V
L0
LINENUMBER 5 L0
GETSTATIC SingleInstance.INSTANCE :LSingleInstance;//注意這里咖耘,我們通過static的方式調(diào)用了該實例
POP
L1
LINENUMBER 6 L1
RETURN
L2
MAXSTACK = 1
MAXLOCALS = 0
// compiled from: Main.kt
}
上面字節(jié)碼文件清晰表明翘簇,kotlin是通過SingleInstance.INSTANCE的方式使用上述實例的,而SingleInstance.INSTANCE正是SingleInstance類生成的儿倒,其字節(jié)碼摘錄如下:
public final static LSingleInstance; INSTANCE
這正是SingleInstance類的唯一一個實例版保,也就是我們通常所說的單例。
看完kotlin字節(jié)碼對應(yīng)的單例夫否,我們不難想象其對應(yīng)于java中實現(xiàn)單例的方法彻犁,這里順便給出,方便有的朋友對二者進(jìn)行比較:
//kotlin單例對應(yīng)的java單例的寫法
public class SingleInstance {
private SingleInstance() {
}
public final static SingleInstance INSTANCE = new SingleInstance();
}
關(guān)于單例我們已經(jīng)講完了凰慈,下面再來看一段關(guān)于object的其他用法的源代碼汞幢,如下所示:
class Test {
public val obj = object {
val i = 1
}
private val obj1 = object {
val i = 1
}
fun test() {
Test().obj1.i//正確
Test().obj.i//!!!錯誤,無法找到變量i
}
}
上面代碼是極其簡單的代碼微谓,重點關(guān)注森篷,為什么在test方法中的obj1可以訪問到其內(nèi)部屬性i,而obj卻無法訪問到其內(nèi)部屬性i豺型?這個就是前面提到的public和private修飾object返回值不同的問題仲智。下面我們看下生成的相關(guān)字節(jié)碼:
public final class Test {//Test類對應(yīng)的字節(jié)碼
// access flags 0x12
private final Ljava/lang/Object; obj
@Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x11
public final getObj()Ljava/lang/Object;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 2 L0
ALOAD 0
GETFIELD Test.obj : Ljava/lang/Object;
ARETURN
L1
LOCALVARIABLE this LTest; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x12
private final LTest$obj1$1; obj1
// access flags 0x1
public <init>()V
L0
LINENUMBER 1 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 2 L1
ALOAD 0
NEW Test$obj$1
DUP
INVOKESPECIAL Test$obj$1.<init> ()V
PUTFIELD Test.obj : Ljava/lang/Object;
L2
LINENUMBER 5 L2
ALOAD 0
NEW Test$obj1$1
DUP
INVOKESPECIAL Test$obj1$1.<init> ()V
PUTFIELD Test.obj1 : LTest$obj1$1;
RETURN
L3
LOCALVARIABLE this LTest; L0 L3 0
MAXSTACK = 3
MAXLOCALS = 1
// access flags 0x19
public final static INNERCLASS Test$obj$1 null null
// access flags 0x19
public final static INNERCLASS Test$obj1$1 null null
// compiled from: Main.kt
}
//注意這里,kotlin編譯器為我們生成了一個新類触创,對應(yīng)于obj
// ================Test$obj$1.class =================
// class version 50.0 (50)
// access flags 0x31
public final class Test$obj$1 {
OUTERCLASS Test <init> ()V
// access flags 0x12
private final I i = 1
// access flags 0x11
public final getI()I
L0
LINENUMBER 3 L0
ALOAD 0
GETFIELD Test$obj$1.i : I
IRETURN
L1
LOCALVARIABLE this LTest$obj$1; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x0
<init>()V
L0
LINENUMBER 2 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 3 L1
ALOAD 0
ICONST_1
PUTFIELD Test$obj$1.i : I
RETURN
L2
LOCALVARIABLE this LTest$obj$1; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x19
public final static INNERCLASS Test$obj$1 null null
// compiled from: Main.kt
}
//注意這里坎藐,kotlin編譯器為我們生成了一個新類,對應(yīng)于obj1
// ================Test$obj1$1.class =================
// class version 50.0 (50)
// access flags 0x31
public final class Test$obj1$1 {
OUTERCLASS Test <init> ()V
// access flags 0x12
private final I i = 1
// access flags 0x11
public final getI()I
L0
LINENUMBER 6 L0
ALOAD 0
GETFIELD Test$obj1$1.i : I
IRETURN
L1
LOCALVARIABLE this LTest$obj1$1; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x0
<init>()V
L0
LINENUMBER 5 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 6 L1
ALOAD 0
ICONST_1
PUTFIELD Test$obj1$1.i : I
RETURN
L2
LOCALVARIABLE this LTest$obj1$1; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x19
public final static INNERCLASS Test$obj1$1 null null
// compiled from: Main.kt
}
字節(jié)碼文件比較長哼绑,照例岩馍,這里找?guī)讉€關(guān)鍵點分析總結(jié)如下:
- kotlin編譯器同樣會為每一個object表達(dá)式生成一個新的類,命名規(guī)則是所在的類名(Test)+$+字段名+數(shù)字(默認(rèn)1)抖韩。其對應(yīng)的字節(jié)碼摘錄如下所示:
public final class Test$obj$1 //obj對應(yīng)的類名
public final class Test$obj1$1 //obj1對應(yīng)的類名
- kotlin編譯器會在所有的新類中為object中的非private修飾(private的修飾的則不會!)的屬性添加一個公有的get方法蛀恩,字節(jié)碼摘錄如下所示:
//obj對應(yīng)的字節(jié)碼
public final getI()I//公有的getI方法
L0
LINENUMBER 3 L0
ALOAD 0
GETFIELD Test$obj$1.i : I
IRETURN
L1
LOCALVARIABLE this LTest$obj$1; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
//obj1對應(yīng)的字節(jié)碼
public final getI()I//公有的getI方法
L0
LINENUMBER 7 L0
ALOAD 0
GETFIELD Test$obj1$1.i : I
IRETURN
L1
LOCALVARIABLE this LTest$obj1$1; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
- 從上面第二點得知,kotlin同時都為obj茂浮、obj1生成了暴露屬性訪問的入口(即公有的get方法)双谆,那么為什么我們在代碼中能通過obj1訪問到i,而卻不能通過obj訪問到i呢席揽?難道kotlin并不是通過暴露的公有方法來訪問其內(nèi)部屬性的顽馋?
不急,我們直接來看下調(diào)用處的字節(jié)碼即可幌羞,但是由于在源碼中寫obj.i這樣的語句是不合法的寸谜,也就是無法編譯,這樣我們就無法看到字節(jié)碼了属桦,所以我們需要對上述源碼做些微小的變更熊痴,先貼出來變更的源碼:
fun test() {
obj1.i
obj//注意這里他爸,僅僅改成了obj語句,并沒有訪問其屬性i
}
上面源碼是合法的果善,obj本身可以作為一個語句诊笤。那么現(xiàn)在就可以看到二者生成的字節(jié)碼有什么不同了,對應(yīng)的字節(jié)碼摘錄如下:
public final test()V//test方法對應(yīng)的字節(jié)碼
L0
LINENUMBER 11 L0
ALOAD 0
GETFIELD Test.obj1 : LTest$obj1$1;//注意這里Test.obj1的類型
INVOKEVIRTUAL Test$obj1$1.getI ()I
POP
L1
LINENUMBER 12 L1
ALOAD 0
GETFIELD Test.obj : Ljava/lang/Object;//注意這里Test.obj的類型
POP
L2
LINENUMBER 13 L2
RETURN
L3
LOCALVARIABLE this LTest; L0 L3 0
MAXSTACK = 1
MAXLOCALS = 1
上面字節(jié)碼中有幾處典型的注釋巾陕,再次摘錄如下:
GETFIELD Test.obj1 : LTest$obj1$1;//注意這里Test.obj1的類型
GETFIELD Test.obj : Ljava/lang/Object;//注意這里Test.obj的類型
這里再看就很明了了讨跟,對于public修飾的object,在調(diào)用的時候?qū)嶋H上被kotlin編譯成了其超類類型對象(如果沒有超類則就是頂層類惜论,這里就是Any類型的對象)许赃,而對于private修飾的object,在調(diào)用的時候?qū)嶋H上被kotlin編譯成了kotlin為其生成的真正的新類型對象馆类。
這就是private修飾的object和public修飾的object的本質(zhì)區(qū)別混聊!有朋友會說如果是其他兩種修飾符呢?即如果是使用protected和internal修飾呢乾巧?答案是這兩種修飾符和public的效果一致句喜,都是生成其超類類型對象。
另外需要說明的是沟于,所謂超類類型對象即是其繼承的父類類型咳胃,如果沒有繼承特定類,在kotlin中則默認(rèn)繼承自Any類型旷太,但是剛剛在字節(jié)碼中明明看到的是java.lang.Object類型展懈?這是為什么?如下所示:
GETFIELD Test.obj : Ljava/lang/Object;//注意這里Test.obj的類型,是java.lang.Object供璧,為什么不是Any存崖?
這個答案放在這回答是顯而易見的,否則只能說你還未了解kotlin睡毒。kotlin實際上是對java的弊端做了諸如語法糖之類的包裝来惧,能夠讓用戶簡單使用的同時保證了與java百分之百的兼容,而其底層實際上正是被編譯成了java的字節(jié)碼演顾,源碼中的Any對應(yīng)于字節(jié)碼層面上就是java.lang.Object供搀。
接下來,再來看一下伴隨對象的實現(xiàn)原理钠至,照例先上我們要分析的源代碼:
class Test {//Test類
companion object {//在Test類中我們生聲明了一個伴隨對象
fun m1() {}
}
}
//測試方法
fun test() {
Test.m1()//使用伴隨對象
}
然后我們將上面代碼生成的字節(jié)碼先粘貼出來:
// ================Test.class =================
// class version 50.0 (50)
// access flags 0x31
public final class Test {//Test類對應(yīng)的字節(jié)碼
// access flags 0x1
public <init>()V
L0
LINENUMBER 1 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this LTest; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x8
static <clinit>()V//注意這里類構(gòu)造初始化方法
NEW Test$Companion
DUP
ACONST_NULL
INVOKESPECIAL Test$Companion.<init> (Lkotlin/jvm/internal/DefaultConstructorMarker;)V
PUTSTATIC Test.Companion : LTest$Companion;
RETURN
MAXSTACK = 3
MAXLOCALS = 0
// access flags 0x19
public final static LTest$Companion; Companion//這里實際上生成了一個public final staitc 的Companion對象
// access flags 0x19
public final static INNERCLASS Test$Companion Test Companion
// compiled from: Main.kt
}
//kotlin編譯器為我們編譯的新類
// ================Test$Companion.class =================
// class version 50.0 (50)
// access flags 0x31
public final class Test$Companion {
// access flags 0x11
public final m1()V//注意m1方法并不是static的
L0
LINENUMBER 3 L0
RETURN
L1
LOCALVARIABLE this LTest$Companion; L0 L1 0
MAXSTACK = 0
MAXLOCALS = 1
// access flags 0x2
private <init>()V//私有構(gòu)造方法葛虐,注定無法自己生成伴隨對象
L0
LINENUMBER 2 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this LTest$Companion; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1001
public synthetic <init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V//這里調(diào)用了上面的私有構(gòu)造方法
L0
LINENUMBER 2 L0
ALOAD 0
INVOKESPECIAL Test$Companion.<init> ()V
RETURN
L1
LOCALVARIABLE this LTest$Companion; L0 L1 0
LOCALVARIABLE $constructor_marker Lkotlin/jvm/internal/DefaultConstructorMarker; L0 L1 1
MAXSTACK = 1
MAXLOCALS = 2
// access flags 0x19
public final static INNERCLASS Test$Companion Test Companion
// compiled from: Main.kt
}
// ================MainKt.class =================
// class version 50.0 (50)
// access flags 0x31
public final class MainKt {
// access flags 0x19
public final static test()V
L0
LINENUMBER 8 L0
GETSTATIC Test.Companion : LTest$Companion;
INVOKEVIRTUAL Test$Companion.m1 ()V
L1
LINENUMBER 9 L1
RETURN
L2
MAXSTACK = 1
MAXLOCALS = 0
// compiled from: Main.kt
}
結(jié)合字節(jié)碼以及上面使用伴隨對象的一些限制,我們來看下其背后的原理棉钧,梳理如下:
- kotlin編譯器同樣會為伴隨對象生成一個新類屿脐,該類的命名規(guī)則是伴隨對象所屬的類名+$+Companion,而這個類有個私有的構(gòu)造方法,因此我們無法自己生成伴隨對象摄悯,對應(yīng)的字節(jié)碼摘錄如下所示:
public final class Test$Companion //kotlin為伴隨對象生成的新類
private <init>()V//構(gòu)造方法是私有的
- kotlin在外部類(Test)進(jìn)行類初始化的時候,就完成了對伴隨對象的初始化愧捕,這就是說伴隨對象的初始化時機(jī)是和其外部類靜態(tài)成員一致奢驯,其對應(yīng)的字節(jié)碼摘錄如下:
//Test類對應(yīng)的類初始化構(gòu)造方法
static <clinit>()V
NEW Test$Companion//這里及以下語句生成了一個Test$Companion類型對象,即是我們所用的伴隨對象
DUP
ACONST_NULL
INVOKESPECIAL Test$Companion.<init> (Lkotlin/jvm/internal/DefaultConstructorMarker;)V
PUTSTATIC Test.Companion : LTest$Companion;
RETURN
MAXSTACK = 3
MAXLOCALS = 0
- 我們在調(diào)用伴隨對象的方法的時候次绘,實際上是使用第二步中生成的實例對象進(jìn)行調(diào)用的瘪阁,這也就是上面所說的伴隨對象中的方法和static方法的本質(zhì)區(qū)別。要證明這一點需要看兩處字節(jié)碼邮偎,分別摘錄如下:
//這是test測試方法對應(yīng)的字節(jié)碼文件
public final static test()V
L0
LINENUMBER 8 L0
GETSTATIC Test.Companion : LTest$Companion;//這里獲取了一個LTest$Companion類型的靜態(tài)變量
INVOKEVIRTUAL Test$Companion.m1 ()V//這里是通過Test$Companion類型實例來完成調(diào)用的
L1
LINENUMBER 9 L1
RETURN
L2
MAXSTACK = 1
MAXLOCALS = 0
//這是Test類對應(yīng)的一部分字節(jié)碼
public final static LTest$Companion; Companion//這句字節(jié)碼就是上段代碼中獲取到的LTest$Companion類型的靜態(tài)變量
上面字節(jié)碼已經(jīng)很清楚了管跺,在伴隨對象所屬的類中,kotlin編譯器為其生成了一個public final static的伴隨對象禾进,在調(diào)用伴隨對象中的方法m1時豁跑,就是通過該實例進(jìn)行調(diào)用的。
那么如果為m1方法加上@JvmStatic注解修飾呢泻云?如下所示:
//這里使用了@JvmStatic來修飾m1方法
class Test {
//Test類
companion object {
//在Test類中我們生聲明了一個伴隨對象
@JvmStatic fun m1() {
}
}
}
我們只需要看下m1方法對應(yīng)生成的字節(jié)碼即可艇拍,如下所示;
public final static m1()V//注意這里,變成了public final static
@Lkotlin/jvm/JvmStatic;()
L0
GETSTATIC Test.Companion : LTest$Companion;
INVOKEVIRTUAL Test$Companion.m1 ()V
RETURN
L1
MAXSTACK = 1
MAXLOCALS = 0
上面字節(jié)碼表明宠纯,使用@JvmStatic注解修飾的方法卸夕,會被kotlin編譯器真正的編譯成static方法!這也正式是使用@JvmStatic修飾和不使用@JvmStatic修飾的伴隨對象方法之間的本質(zhì)區(qū)別婆瓜。
至此快集,kotlin的object相關(guān)內(nèi)容已經(jīng)闡述完畢。