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
变量和方法实现有疑问的同学,希望能有所帮助。