kotlin入門潛修之類和對象篇—object及其原理

本文收錄于 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)
    }
}

上面代碼需要注意以下幾點:

  1. 如果父類構(gòu)造方法有入?yún)⑺┣瑒t在生成匿名對象時必須要按父類構(gòu)造方法的定義傳參。
  2. 由于生成的匿名對象有多個父實現(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作為屬性存在時有一些限制:

  1. 匿名object作為private屬性時革砸,其表達(dá)式返回的類型就是object類型
  2. 匿名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作為聲明時需要注意以下幾點:

  1. object作為聲明時是沒有右值的赴恨,所以無法像上一章節(jié)中作為表達(dá)式那樣賦值給其他變量疹娶。
  2. object作為聲明時無法聲明本地變量,但可以作為類成員存在伦连。
  3. 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é)以下幾點:

  1. 使用object修飾后之所以是單例的冤吨,是因為其構(gòu)造方法是private的,我們無法在外部進(jìn)行對象生成陷寝,其對應(yīng)的字節(jié)碼摘錄如下:
  private <init>()V//私有的構(gòu)造方法
  1. 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é)如下:

  1. 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)的類名
  1. 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
  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é)碼以及上面使用伴隨對象的一些限制,我們來看下其背后的原理棉钧,梳理如下:

  1. kotlin編譯器同樣會為伴隨對象生成一個新類屿脐,該類的命名規(guī)則是伴隨對象所屬的類名+$+Companion,而這個類有個私有的構(gòu)造方法,因此我們無法自己生成伴隨對象摄悯,對應(yīng)的字節(jié)碼摘錄如下所示:
public final class Test$Companion //kotlin為伴隨對象生成的新類
private <init>()V//構(gòu)造方法是私有的
  1. 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
  1. 我們在調(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)闡述完畢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末廉白,一起剝皮案震驚了整個濱河市个初,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蒙秒,老刑警劉巖勃黍,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異晕讲,居然都是意外死亡覆获,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進(jìn)店門瓢省,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弄息,“玉大人,你說我怎么就攤上這事勤婚∧×浚” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缨称。 經(jīng)常有香客問我凝果,道長,這世上最難降的妖魔是什么睦尽? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任器净,我火速辦了婚禮,結(jié)果婚禮上当凡,老公的妹妹穿的比我還像新娘山害。我一直安慰自己,他們只是感情好沿量,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布浪慌。 她就那樣靜靜地躺著,像睡著了一般朴则。 火紅的嫁衣襯著肌膚如雪权纤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天乌妒,我揣著相機(jī)與錄音妖碉,去河邊找鬼。 笑死芥被,一個胖子當(dāng)著我的面吹牛欧宜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拴魄,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼冗茸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了匹中?” 一聲冷哼從身側(cè)響起夏漱,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎顶捷,沒想到半個月后挂绰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡服赎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年葵蒂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片重虑。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡践付,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缺厉,到底是詐尸還是另有隱情永高,我是刑警寧澤隧土,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站命爬,受9級特大地震影響曹傀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜饲宛,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一卖毁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧落萎,春花似錦炭剪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽错妖。三九已至,卻和暖如春暂氯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痴施。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留辣吃,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓神得,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哩簿。 傳聞我的和親對象是個殘疾皇子宵蕉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容