kotlin
在被欽定為Android
的官方開發語言後,越來越多的Android
開發者投向kotlin
的懷抱。儘管kotlin
相容Java
,但在使用上還是有很大不同的,就像static
關鍵字,我們可以用companion object
來替代static
,當我們用反射去呼叫時,會發現呼叫時並不像static
那樣直接,筆者在日常使用中就遇到這樣的問題,想拿反射去呼叫靜態方法時無法呼叫,所以便通過位元組碼的實現來一窺究竟,順便水一篇文章(●>∀<●)。
一、如何檢視kotlin位元組碼
我們通過Tools->Kotlin->Show Kotlin bytecode
開啟Kotlin
位元組碼介面,檢視Kotlin
檔案的位元組碼形式。介面如下:
二、Object單例看static
在Kotlin
中,我們可以通過Object
來直接實現一個單例,通過對Object
單例中方法的呼叫來實現類似於Java
中static
方法的呼叫。
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
在該類中宣告瞭一個INSTANCE
的static
變數來實現單例效果。
所以我們在Java
語言中呼叫foo()
方法是這樣的,即拿到INSTANCE
靜態變數再繼續呼叫。
三、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
單例的呼叫是這樣的
四、通過@JvmStatic實現Java中的靜態方法
通過以上兩個例子,我們發現,在我們宣告的單例中,變數是採用了static
修飾的,我們通過反射可以直接拿到變數。而方法都沒有使用static
修飾。如果不加處理,在我們用Java
進行反射呼叫時,我們無法對foo()方法像Java
的static
方法進行直接的反射呼叫,而要通過Object
單例中的INSTANCE
或者使用Companion Object
單例時的Companion
靜態變數,間接地進行反射呼叫。
那麼,我們可不可以像對這些單例的方法,進行Java
的static
方法的反射呼叫呢?這時候我們就要使用@JvmStatic
註解。
這時候我們就可以看到foo()
方法也被static
修飾了,這樣我們在呼叫foo()
方法的方式和在Java
呼叫時的是一致的了。
五、結論
從上面我們可以看到,如果不通過@JvmStatic
註解,kotlin在位元組碼中是不產生static
方法的,當然我們在kotlin
使用中是可以直接呼叫,如MyClass.foo()
的,而放到Java
上表現就明顯不同了。這篇文章主要是寫給Java
轉向kotlin
時對kotlin
中static
變數和方法實現有疑問的同學,希望能有所幫助。