通过字节码看原理,带你去找kotlin中的static方法

kotlin在被钦定为Android的官方开发语言后,越来越多的Android开发者投向kotlin的怀抱。尽管kotlin兼容Java,但在使用上还是有很大不同的,就像static关键字,我们可以用companion object来替代static,当我们用反射去调用时,会发现调用时并不像static那样直接,笔者在日常使用中就遇到这样的问题,想拿反射去调用静态方法时无法调用,所以便通过字节码的实现来一窥究竟,顺便水一篇文章(●>∀<●)。

一、如何查看kotlin字节码

我们通过Tools->Kotlin->Show Kotlin bytecode打开Kotlin字节码界面,查看Kotlin文件的字节码形式。界面如下:

二、Object单例看static

Kotlin中,我们可以通过Object来直接实现一个单例,通过对Object单例中方法的调用来实现类似于Javastatic方法的调用。

1
2
3
4
5
6
7
object MyObject {
val x = "x"

public fun foo(): String {
return x
}
}

对于这个简单的Object单例,我们看到的字节码是这样的(省略部分字节码):

1
2
3
4
5
6
7
8
9
10
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静态变量再继续调用。

三、Companion Object单例看static

这一次,我们通过Companion Object伴生对象来实现静态的变量和方法调用,代码如下:

1
2
3
4
5
6
7
8
9
10
class MyClass {

companion object {
val x = "x"

fun foo(): String {
return x
}
}
}

我们看到的字节码是这样的(省略部分字节码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

// 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()方法像Javastatic方法进行直接的反射调用,而要通过Object单例中的INSTANCE或者使用Companion Object单例时的Companion静态变量,间接地进行反射调用。

那么,我们可不可以像对这些单例的方法,进行Javastatic方法的反射调用呢?这时候我们就要使用@JvmStatic注解。

这时候我们就可以看到foo()方法也被static修饰了,这样我们在调用foo()方法的方式和在Java调用时的是一致的了。

五、结论

从上面我们可以看到,如果不通过@JvmStatic注解,kotlin在字节码中是不产生static方法的,当然我们在kotlin使用中是可以直接调用,如MyClass.foo()的,而放到Java上表现就明显不同了。这篇文章主要是写给Java转向kotlin时对kotlinstatic变量和方法实现有疑问的同学,希望能有所帮助。

推荐文章