Kotlin的语法是非常精简的, 本文分析常见的Kotlin语法生成的字节码, 加深对Kotlin的理解。
分析字节码的工具
使用Idea的"Kotlin Bytecode"插件, 使用也很简单, 只要打开"Kotlin Bytecode"窗口,就会自动显示当前窗口的kotlin代码的Java字节码。 如果觉得看字节码很麻烦, 也可以用“DeCompile”功能反编译成Java代码。
Getter Setter
Java的Getter Setter代码使用很广泛, 但是写起来也是真的折磨, 全都是模版代码。 Kotlin的val/var关键字就有Getter/Setter功能。
可以看到,java类里value1/value2成员变量都是私有的
然后生成了3个方法。
变量value1是用val修饰的, 只有getValue1方法。
变量value2是用va2修饰的, 有getValue2方法, setValue2方法。
Kotlin
class Foo {
val value1: String = "1"
var value2: Int = 2
}Decompiled Java ByteCode
public final class Foo {
@NotNull
private final String value1 = "1";
private int value2 = 2;
public static final int $stable = 8;
@NotNull
public final String getValue1() {
return this.value1;
}
public final int getValue2() {
return this.value2;
}
public final void setValue2(int var1) {
this.value2 = var1;
}
}object
object关键字修饰的类是一个单例对象。
反编译的Java代码中Foo类有一个静态成员INSTANCE, 它是在类静态初始化方法(static ()V)中被赋值的。
还有成员变量meme也是static final修饰的,证明这个成员也是属于类的。
当使用Foo.meme时, 生成的字节码是:
Foo.INSTANCE.getMeme();
所以使用这个单例对象本质上是使用Foo的静态成员INSTANCE。
Kotlin
object Foo {
val meme: String = "1"
init {
val a = "1111"
}
}Decompiled Java ByteCode
public final class Foo {
@NotNull
public static final Foo INSTANCE = new Foo();
@NotNull
private static final String meme = "1";
public static final int $stable;
private Foo() {
}
@NotNull
public final String getMeme() {
return meme;
}
static {
String var0 = "1111";
}
}
函数重载
Java的函数重载是每种形式都需要定义一个方法,十分麻烦。
Kotlin则不需要,Kotlin额外生成了一个静态方法
static void bar$default(Foo var0, String var1, String var2, String var3, int var4, Object var5)
var0是reciver,
var1 var2 var3是 方法bar的3个参数
var4是一个二进制状态位,标记哪个参数使用初始值。 下面的例子传的是6,二进制是"110", 表示第二个和第三个参数都使用初始值。
kotlin
class Foo {
fun bar(para1: String, para2: String = "11", para3: String = "22") {}
}
// Call method
Foo().bar(para1 = "para1")Decompiled Java ByteCode
public final class Foo {
public static final int $stable;
public final void bar(@NotNull String para1, @NotNull String para2, @NotNull String para3) {
...
}
// $FF: synthetic method
public static void bar$default(Foo var0, String var1, String var2, String var3, int var4, Object var5) {
if ((var4 & 2) != 0) {
var2 = "11";
}
if ((var4 & 4) != 0) {
var3 = "22";
}
var0.bar(var1, var2, var3);
}
}
// Call method
Foo.bar$default(new Foo(), "para1", (String)null, (String)null, 6, (Object)null);data class
Kotlin的数据类会生成以下方法。
Foo(String var1, String var2, int var3, DefaultConstructorMarker var4) 额外的构造函数, 用来做函数重载
toString
copy
copy$default 函数重载用
equals
componentN 解构语法用
data class Foo(
val para1: String,
val para2: String = "",
)
// deconstruct
val (a, b) = Foo("")Decompiled Java ByteCode
public final class Foo {
@NotNull
private final String para1;
@NotNull
private final String para2;
public static final int $stable;
...
@NotNull
public final String component1() {
return this.para1;
}
@NotNull
public final String component2() {
return this.para2;
}
@NotNull
public final Foo copy(@NotNull String para1, @NotNull String para2) {
..
return new Foo(para1, para2);
}
// $FF: synthetic method
public static Foo copy$default(Foo var0, String var1, String var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var1 = var0.para1;
}
if ((var3 & 2) != 0) {
var2 = var0.para2;
}
return var0.copy(var1, var2);
}
@NotNull
public String toString() {
return "Foo(para1=" + this.para1 + ", para2=" + this.para2 + ')';
}
public int hashCode() {
int result = this.para1.hashCode();
result = result * 31 + this.para2.hashCode();
return result;
}
public boolean equals(@Nullable Object other) {
...
}
}
// deconstruct
Foo var0 = new Foo("", (String)null, 2, (DefaultConstructorMarker)null);
String a = var0.component1();
String b = var0.component2();extension function
Kotlin中带有Reciver的扩展函数本质上是一个静态函数, 函数的第一个参数是该Reciver。
class Foo {
}
fun Foo.extension() {
} public static final void extension(@NotNull Foo $this$extension) {
Intrinsics.checkNotNullParameter($this$extension, "<this>");
}inline function
可以看到如果用inline修饰函数, block里的内容就被展开到调用方。 如果没有inline, 就是正常的函数调用(传入一个lambda)
class Foo {
inline fun bar(block: () -> Unit) {
block()
}
}
fun main() {
val foo = Foo()
foo.bar {
println("hello")
}
}with inline
public static final void main() {
Foo foo = new Foo();
int $i$f$bar = false;
int var3 = false;
String var4 = "hello";
System.out.println(var4);
}without inline
public static final void main() {
Foo foo = new Foo();
foo.bar(TestKt::main$lambda$0);
}
private static final Unit main$lambda$0() {
String var0 = "hello";
System.out.println(var0);
return Unit.INSTANCE;
}companion
companion object 本质上是一个静态的内部类, 他的名字默认是Companion, 如果这样声明, companion object A, 则生成的内部类的名字是A。
Foo 有一个静态的成员Companion,用类名来引用的时候都是引用的这个静态成员Companion
由于是一个内部类,伴生对象还可以实现接口,语法如下
companion object A: Interface
生成的内部类为
public static final class A implements Interface
class Foo {
companion object {
}
}public final class Foo {
@NotNull
public static final Companion Companion = new Companion((DefaultConstructorMarker)null);
public static final class Companion {
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}Delegation
- 类代理
代理的类会生成一个私有成员$$delegate_0,它在构造函数中初始化。 并且重写所有的接口方法, 默认实现是直接调用这个类的对应方法。
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by bpublic final class Derived implements Base {
// $FF: synthetic field
private final Base $$delegate_0;
public Derived(@NotNull Base b) {
Intrinsics.checkNotNullParameter(b, "b");
super();
this.$$delegate_0 = b;
}
public void print() {
this.$$delegate_0.print();
}- 值代理
值的代理对象是一个私有成员变量p$delegate, 在生成的Getter/Setter方法中
调用这个代理对象的getter/setter方法
。
代理的getter/setter有一个property: KProperty<*>参数, 这个是别代理的成员的
类型。 可以看到这个值是从$$delegatedProperties取得的
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}public final class Example {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties;
@NotNull
private final Delegate p$delegate = new Delegate();
public static final int $stable;
@NotNull
public final String getP() {
return this.p$delegate.getValue(this, $$delegatedProperties[0]);
}
public final void setP(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.p$delegate.setValue(this, $$delegatedProperties[0], var1);
}
static {
KProperty[] var0 = new KProperty[]{Reflection.mutableProperty1((MutablePropertyReference1)(new MutablePropertyReference1Impl(Example.class, "p", "getP()Ljava/lang/String;", 0)))};
$$delegatedProperties = var0;
}
}suspend
class Example {
suspend fun foo() {
aaa()
bbb()
}
suspend fun aaa(): String {
return ""
}
suspend fun bbb() {
}
}首先看生成的函数aaa,
kotlin中定义的返回值是String,
生成的java字节码的返回值是Object, 并且多了一个参数Continuation。
说明suspend函数都隐藏了一个Continuation参数, 而普通函数不能提供这个Continuation对象,所以普通函数不能直接调用suspend函数。
@Nullable
public final Object aaa(@NotNull Continuation $completion) {
return "";
}
当一个suspend修饰的函数中有2个或两个以上的挂起函数时,编译器会生成一个内部类,
用来管理挂起函数的状态。
这个生成的内部类Example$foo$1继承了ContinuationImpl。
synthetic Ljava/lang/Object; result 这个成员记录了invokeSuspend传入的
参数。
I label 一个Int变量,用来记录挂起点的位置。
invokeSuspend函数中,取得label的值, 并把最高位置为1。
然后调用了Example.foo(this), 因为这个Example$foo$1是内部类,
所以有外部类的引用, 并且foo传入的参数是内部类this实例。
final class kotlinx/serialization/Example$foo$1 extends kotlin/coroutines/jvm/internal/ContinuationImpl {
// compiled from: Test.kt
OUTERCLASS kotlinx/serialization/Example foo (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@Lkotlin/Metadata;(mv={2, 1, 0}, k=3, xi=50)
// access flags 0x18
final static INNERCLASS kotlinx/serialization/Example$foo$1 null null
// access flags 0x0
Ljava/lang/Object; L$0
// access flags 0x1000
synthetic Ljava/lang/Object; result
// access flags 0x1010
final synthetic Lkotlinx/serialization/Example; this$0
// access flags 0x0
I label
// access flags 0x0
// signature (Lkotlinx/serialization/Example;Lkotlin/coroutines/Continuation<-Lkotlinx/serialization/Example$foo$1;>;)V
...
// access flags 0x11
public final invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
@Lorg/jetbrains/annotations/Nullable;() // invisible
// annotable parameter count: 1 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
ALOAD 1
PUTFIELD kotlinx/serialization/Example$foo$1.result : Ljava/lang/Object;
ALOAD 0
ALOAD 0
GETFIELD kotlinx/serialization/Example$foo$1.label : I
LDC -2147483648
IOR
PUTFIELD kotlinx/serialization/Example$foo$1.label : I
ALOAD 0
GETFIELD kotlinx/serialization/Example$foo$1.this$0 : Lkotlinx/serialization/Example;
ALOAD 0
CHECKCAST kotlin/coroutines/Continuation
INVOKEVIRTUAL kotlinx/serialization/Example.foo (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
ARETURN
L1
LOCALVARIABLE this Lkotlinx/serialization/Example$foo$1; L0 L1 0
LOCALVARIABLE $result Ljava/lang/Object; L0 L1 1
MAXSTACK = 3
MAXLOCALS = 2
}
接下来看foo函数的实现。
首先判断传入参数$completion的类型是不是生成的内部类Example$foo$1,
如果不是, 则初始化。如果是, 则将内部类里的label的最高位置0, 表明挂起函数继续运行。
假设label是初始值0, 则调用挂起函数aaa,并判断返回值是否是挂起标志位COROUTINE_SUSPENDED
如果是, 则直接返回挂起标志, 结束当前函数运行。 如果不是, 则继续执行bbb。
如果aaa返回了挂起标志位, 表示aaa函数不能立即返回结果, 可能是需要在另一个线程上做异步, 也可能是把continuation实例保存起来了,将来调用。 不管是哪种情况。
aaa函数负责调用continuation.resume来恢复执行。
上面说过,aaa函数得到的continuation的类型是内部类Example$foo$1, 他继承了
ContinuationImpl, ContinuationImpl.resumeWith的内部实现贴到下面了。
resumeWith调用了invokeSuspend,
他的实现就是上面的字节码,首先取得label的值, 并把最高位置为1。
然后调用了Example.foo(this), 让foo函数重新执行, 由于之前设置的label是1
,函数会从aaa的后一行开始执行。
public final class Example {
@Nullable
public final Object foo(@NotNull Continuation $completion) {
Object $continuation;
label27: {
if ($completion instanceof Example$foo$1) {
$continuation = (Example$foo$1)$completion;
if ((((Example$foo$1)$continuation).label & Integer.MIN_VALUE) != 0) {
((Example$foo$1)$continuation).label -= Integer.MIN_VALUE;
break label27;
}
}
$continuation = new Example$foo$1($completion);
}
Object $result = ((<undefinedtype>)$continuation).result;
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (((<undefinedtype>)$continuation).label) {
case 0:
ResultKt.throwOnFailure($result);
((<undefinedtype>)$continuation).L$0 = this;
((<undefinedtype>)$continuation).label = 1;
if (this.aaa((Continuation)$continuation) == var4) {
return var4;
}
break;
case 1:
this = (Example)((<undefinedtype>)$continuation).L$0;
ResultKt.throwOnFailure($result);
break;
case 2:
ResultKt.throwOnFailure($result);
return Unit.INSTANCE;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
((<undefinedtype>)$continuation).L$0 = null;
((<undefinedtype>)$continuation).label = 2;
if (this.bbb((Continuation)$continuation) == var4) {
return var4;
} else {
return Unit.INSTANCE;
}
}
public final override fun resumeWith(result: Result<Any?>) {
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
var current = this
var param = result
while (true) {
// Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
// can precisely track what part of suspended callstack was already resumed
probeCoroutineResumed(current)
with(current) {
val completion = completion!! // fail fast when trying to resume continuation without completion
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted() // this state machine instance is terminating
if (completion is BaseContinuationImpl) {
// unrolling recursion via loop
current = completion
param = outcome
} else {
// top-level completion reached -- invoke and return
completion.resumeWith(outcome)
return
}
}
}
}