通過位元組碼看原理,帶你去找kotlin中的static方法

Windin發表於2018-11-04

kotlin在被欽定為Android的官方開發語言後,越來越多的Android開發者投向kotlin的懷抱。儘管kotlin相容Java,但在使用上還是有很大不同的,就像static關鍵字,我們可以用companion object來替代static,當我們用反射去呼叫時,會發現呼叫時並不像static那樣直接,筆者在日常使用中就遇到這樣的問題,想拿反射去呼叫靜態方法時無法呼叫,所以便通過位元組碼的實現來一窺究竟,順便水一篇文章(●>∀<●)。

一、如何檢視kotlin位元組碼

我們通過Tools->Kotlin->Show Kotlin bytecode開啟Kotlin位元組碼介面,檢視Kotlin檔案的位元組碼形式。介面如下:

通過位元組碼看原理,帶你去找kotlin中的static方法

二、Object單例看static

Kotlin中,我們可以通過Object來直接實現一個單例,通過對Object單例中方法的呼叫來實現類似於Javastatic方法的呼叫。

object MyObject {
    val x = "x"

    public fun foo(): String {
        return x
    }
}
複製程式碼

對於這個簡單的Object單例,我們看到的位元組碼是這樣的(省略部分位元組碼):

  private final static Ljava/lang/String; x = "x"
  
  public final getX()Ljava/lang/String;
  ...
  public final setX(Ljava/lang/String;)V
  ..
  public final foo()Ljava/lang/String;
  ...
  
  public final static Lcom/tanzhouedu/testapplication/MyObject; INSTANCE
複製程式碼

可以看到,Kotlin在該類中宣告瞭一個INSTANCEstatic變數來實現單例效果。

所以我們在Java語言中呼叫foo()方法是這樣的,即拿到INSTANCE靜態變數再繼續呼叫。

通過位元組碼看原理,帶你去找kotlin中的static方法

三、Companion Object單例看static

這一次,我們通過Companion Object伴生物件來實現靜態的變數和方法呼叫,程式碼如下:

class MyClass {

    companion object {
        val x = "x"

        fun foo(): String {
            return x
        }
    }
}
複製程式碼

我們看到的位元組碼是這樣的(省略部分位元組碼):


// access flags 0x1A
private final static Ljava/lang/String; x = "x"

// access flags 0x19
public final static Lcom/windinwork/myapplication/bytecode/MyClass$Companion; Companion
  
// access flags 0x31
public final class com/windinwork/myapplication/bytecode/MyClass$Companion {
  // access flags 0x11
  public final getX()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 6 L0
    INVOKESTATIC com/windinwork/myapplication/bytecode/MyClass.access$getX$cp ()Ljava/lang/String;
    ARETURN
   ...

  // access flags 0x11
  public final foo()Ljava/lang/String;
   ...

複製程式碼

我們來分析一下這段位元組碼,可以看到,我們在Companion Object中宣告的變數x,編譯之後是作為MyClass的靜態變數存在,而方法getX()和foo()是作為MyClass$Companion的成員方法存在。我們可以看到,MyClass通過一個靜態變數Companion持有MyClass$Companion的引用,所以我們在訪問x變數和呼叫foo()方法時,實質上是通過對Companion這一靜態變數進行方法呼叫,於是我們在Java中對Companion Object單例的呼叫是這樣的

通過位元組碼看原理,帶你去找kotlin中的static方法

四、通過@JvmStatic實現Java中的靜態方法

通過以上兩個例子,我們發現,在我們宣告的單例中,變數是採用了static修飾的,我們通過反射可以直接拿到變數。而方法都沒有使用static修飾。如果不加處理,在我們用Java進行反射呼叫時,我們無法對foo()方法像Javastatic方法進行直接的反射呼叫,而要通過Object單例中的INSTANCE或者使用Companion Object單例時的Companion靜態變數,間接地進行反射呼叫。

那麼,我們可不可以像對這些單例的方法,進行Javastatic方法的反射呼叫呢?這時候我們就要使用@JvmStatic註解。

通過位元組碼看原理,帶你去找kotlin中的static方法

這時候我們就可以看到foo()方法也被static修飾了,這樣我們在呼叫foo()方法的方式和在Java呼叫時的是一致的了。

通過位元組碼看原理,帶你去找kotlin中的static方法

五、結論

從上面我們可以看到,如果不通過@JvmStatic註解,kotlin在位元組碼中是不產生static方法的,當然我們在kotlin使用中是可以直接呼叫,如MyClass.foo()的,而放到Java上表現就明顯不同了。這篇文章主要是寫給Java轉向kotlin時對kotlinstatic變數和方法實現有疑問的同學,希望能有所幫助。

相關文章