直面底层之字节码看kotlin语法糖

948 阅读6分钟

前言

kotlin 新增了一些功能,诸如 object单例、顶层函数、扩展函数等,但是这些在java 层并没有指令对应,那java 层是如何处理这些case 呢? 本文从字节码层面来看一下这些情况的真身。


一、顶层函数

kotlin 代码示例:

//Hello.kt
fun topMethod(a:String){
    println(a)
}

很简单的一个函数,传入一个参数然后输出,我们用kotlinc 编译下 Hello.kt,看到生成了 HelloKt.class,用javap 看下 HelloKt.class的字节码:

Compiled from "Hello.kt"
public final class basic.jvmkotlin.HelloKt {
  public static final void topMethod(java.lang.String);
    Code:
       0: aload_0
       1: ldc           #9   // String a
       3: invokestatic  #15 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: iconst_0
       7: istore_1
       8: getstatic     #21  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_0
      12: invokevirtual #27 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      15: return
}

代码也很简单,生成了HelloKt.class,然后生成了 staic final 的 topMethod 的静态类方法,然后调用 System.out.println 方法,对应的java 代码如下:

@Metadata(
   mv = {1, 4, 1},    //kotlin  版本 及 1.4.1
   bv = {1, 0, 3},    //编译版本 1.0.3
   k = 2,             //kotlin 文件类型 1 class  2 file
   //d1 是pb编码后的b2  
   d1 =   {"\u0000\f\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u000e\u001a\u000e\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0003"},
   d2 = {"topMethod", "", "a", ""}
)
public final class HelloKt {
   public static final void topMethod(@NotNull String a) {
      Intrinsics.checkNotNullParameter(a, "a");
      boolean var1 = false;
      System.out.println(a);
   }
}
小结:

koltin顶层函数底层会生成 kolitn文件名 + Kt 对应的 java class 文件,java class 文件内部会将顶层方法包装成 static final 的 同名类方法。


二、扩展函数

示例代码:

fun String.repeatPrint(){

}

我们给String扩展了repeatPrint 方法,接下来我们看下字节码:

public final class basic.jvmkotlin.HelloKt {
  public static final void repeatPrint(java.lang.String);
    Code:
       0: aload_0
       1: ldc           #9                  // String $this$repeatPrint
       3: invokestatic  #15                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: return
}

从字节码中看到 生成了一个静态方法,入参是 String,对应的java代码是:

public final class HelloKt {
   public static final void repeatPrint(@NotNull String $this$repeatPrint) {
      Intrinsics.checkNotNullParameter($this$repeatPrint, "$this$repeatPrint");
   }
}
小结:

扩展函数的本质是 生成了一个 静态函数,并传入了 扩展函数调用者的实例对象


三、object 单例

Kotlin 单例相比 java 的单例变得非常简单,如下代码就是一个单例:

object  SingleObject {
}

我们来看下字节码:

public final class basic.jvmkotlin.SingleObject {
  public static final basic.jvmkotlin.SingleObject INSTANCE;

  static {};
    Code:
       0: new           #2                  // class basic/jvmkotlin/SingleObject
       3: dup
       4: invokespecial #26                 // Method "<init>":()V
       7: astore_0
       8: aload_0
       9: putstatic     #28                 // Field INSTANCE:Lbasic/jvmkotlin/SingleObject;
      12: return
}

首先是一个静态的变量 INSTANCE; 然后是一个静态代码块 及类的 cinit 函数,调用 init() 即构造函数,然后赋值给 INSTANCE;这是典型的 饿汉式单例,对应的java 代码:

public final class SingleObject {
   @NotNull
   public static final SingleObject INSTANCE;

   private SingleObject() {
   }

   static {
      SingleObject var0 = new SingleObject();
      INSTANCE = var0;
   }
}

小结:

Kotlin 单例的实现原理对应了 java 饿汉式单例的模式


四、接口默认方法

kotlin接口可以有默认实现(java 8 也可以有默认实现),但是底层的实现原理却不一样我们来看下koltin 默认的接口实现:

//接口
interface KoltlinBaseInterface {
    fun kolinBasePrint(){
        println();
    }
}

//实现
class KoltlinImpInterface :KoltlinBaseInterface{
    override fun kolinBasePrint() {
        super.kolinBasePrint()
    }
}

我们用kotlinc 编译 看下编译后的 :

//接口类
public interface KoltlinBaseInterface {
   void kolinBasePrint();

   @Metadata(
      mv = {1, 4, 1},
      bv = {1, 0, 3},
      k = 3
   )
  //默认接口实现 生成了静态类 DefaultImpls,并生成了 静态方法 kolinBasePrint
   public static final class DefaultImpls {
      public static void kolinBasePrint(@NotNull KoltlinBaseInterface $this) {
         boolean var1 = false;
         System.out.println();
      }
   }
}


//实现类
public final class KoltlinImpInterface implements KoltlinBaseInterface {
   public void kolinBasePrint() {
      //实现类 调用了 接口类默认生成的静态类的静态方法 并传入了实现类的引用
      DefaultImpls.kolinBasePrint(this);
   }
}
小结:

kotlin 接口默认实现的原理是 生成了 静态类,静态类实现了静态方法,及默认的实现逻辑,实现类 调用默认实现逻辑是通过 调用生成的静态内部类的静态方法。


五、默认属性

我们知道java 定义一个类如果增加或者删除属性 构造函数也会需要改变,而Kotlin 提供了缺省参数的机制,我们来看下原理

//TestData类,name 是必传值,int 和 female 有默认值 
class TestData(var name:String,var age:Int = 18, var female :String = "男") {
}

//调用方 分别调用了 传入一个 参数 两个参数  三个参数的构造方法
fun main() {
    TestData("ydg")
    TestData("ydg",1)
    TestData("ydg",2,"女")
}

我们来看下字节码:

//TestData字节码
public final class basic.jvmkotlin.TestData {
  // getName() 方法
  public final java.lang.String getName();
    Code:
       0: aload_0
       1: getfield      #11                 // Field name:Ljava/lang/String;
       4: areturn
  //setName 方法
  public final void setName(java.lang.String);
    Code:
       0: aload_1
       1: ldc           #17                 // String <set-?>
       3: invokestatic  #23                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: aload_1
       8: putfield      #11                 // Field name:Ljava/lang/String;
      11: return

  public final int getAge();
    Code:
       0: aload_0
       1: getfield      #29                 // Field age:I
       4: ireturn

  public final void setAge(int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #29                 // Field age:I
       5: return

  public final java.lang.String getFemale();
    Code:
       0: aload_0
       1: getfield      #35                 // Field female:Ljava/lang/String;
       4: areturn

  public final void setFemale(java.lang.String);
    Code:
       0: aload_1
       1: ldc           #17                 // String <set-?>
       3: invokestatic  #23                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: aload_1
       8: putfield      #35                 // Field female:Ljava/lang/String;
      11: return
  //三个参数的构造方法
  public basic.jvmkotlin.TestData(java.lang.String, int, java.lang.String);
    Code:
       0: aload_1
       1: ldc           #39                 // String name
       3: invokestatic  #23                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_3
       7: ldc           #40                 // String female
       9: invokestatic  #23                 // Method kotlin/jvm/internal/Intrinsics.checkNotNullParameter:(Ljava/lang/Object;Ljava/lang/String;)V
      12: aload_0
      13: invokespecial #43                 // Method java/lang/Object."<init>":()V
      16: aload_0
      17: aload_1
      18: putfield      #11                 // Field name:Ljava/lang/String;
      21: aload_0
      22: iload_2
      23: putfield      #29                 // Field age:I
      26: aload_0
      27: aload_3
      28: putfield      #35                 // Field female:Ljava/lang/String;
      31: return
  //5 个参数的构造方法
  public basic.jvmkotlin.TestData(java.lang.String, int, java.lang.String, int, kotlin.jvm.internal.DefaultConstructorMarker);
    Code:
       0: iload         4  //加载局部变量表第四个 即 参数 int
       2: iconst_2         //加载 2
       3: iand             //进行与操作
       4: ifeq          10 //如果相等向下执行
       7: bipush        18 //年龄默认18 放入局部变量表第二个位置
       9: istore_2
      
      10: iload         4   //加载局部变量表第四 及参数 int
      12: iconst_4          // 加载常量 4
      13: iand              // 与操作
      14: ifeq          20  // 相等说明该参数没有值 向下执行
      17: ldc           #46 // String 男 第三个参数复制默认值
      19: astore_3
      //加载三个参数 调用三个参数的构造方法
      20: aload_0
      21: aload_1
      22: iload_2
      23: aload_3
      24: invokespecial #48                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
      27: return
}

//main字节码
public final class basic.jvmkotlin.KotlinTestKt {
  public static final void main();
    Code:
       //对应第一个new  调用了五个参数的构造方法,第四个传入的是 6 (110)代表第二个位和第三位参数缺省
       0: new           #11     // class basic/jvmkotlin/TestData
       3: dup
       4: ldc           #13    // String ydg
       6: iconst_0
       7: aconst_null
       8: bipush        6
      10: aconst_null
      11: invokespecial #17  // Method basic/jvmkotlin/TestData."<init>":(Ljava/lang/String;ILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
      14: pop
        
      // 调用了 五个参数的构造方法  第四个参数传入的是   4(100)代表第三位参数缺省
      15: new           #11   // class basic/jvmkotlin/TestData
      18: dup
      19: ldc           #13   // String ydg
      21: iconst_1
      22: aconst_null
      23: iconst_4
      24: aconst_null
      25: invokespecial #17                 // Method basic/jvmkotlin/TestData."<init>":(Ljava/lang/String;ILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
      28: pop
      //调用了三个参数的构造方法 
      29: new           #11                 // class basic/jvmkotlin/TestData
      32: dup
      33: ldc           #13                 // String ydg
      35: iconst_2
      36: ldc           #19                 // String 女
      38: invokespecial #22                 // Method basic/jvmkotlin/TestData."<init>":(Ljava/lang/String;ILjava/lang/String;)V
      41: pop
      42: return

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #9                  // Method main:()V
       3: return
}

//对应的java 代码
public final class KotlinTestKt {
   public static final void main() {
      new TestData("ydg", 0, (String)null, 6, (DefaultConstructorMarker)null);
      new TestData("ydg", 1, (String)null, 4, (DefaultConstructorMarker)null);
      new TestData("ydg", 2, "女");
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}
小结

可以看到TestData 多了一个五个参数的构造方法,第四个参数 int 用位运算 来确定第几个参数是缺省的可以使用默认值,先使用位运算给缺省属性赋值,然后调用 三个参数的构造方法.

特殊情况:

既然缺省位数是用int 的bit 位表示,int 最多32位,如果超过32个属性,那么该怎么表示呢?

定义了一个 33个可以缺省的属性,看下 多了两个 int var34 和 var35,var 34 表示前面32个参数的缺省情况,var33 则由 var35的决定是否缺省

如果超出32个属性可以默认,那么会在增加一个int 参数继续表示后面的位数缺省情况,即由五个参数变成六个参数(感兴趣的可以看下字节码)

public TestDataLimit(int var1, int var2, int var3, int var4, int var5, int var6, int var7, int var8, int var9, int var10, int var11, int var12, int var13, int var14, int var15, int var16, int var17, int var18, int var19, int var20, int var21, int var22, int var23, int var24, int var25, int var26, int var27, int var28, int var29, int var30, int var31, int var32, int var33, int var34, int var35, DefaultConstructorMarker var36) 

六、for 循环

下面一个简单的for循环:

//kotlin for 循环
fun testFor(){
    for(i in 100 downTo 1 step 3){
        
    }
}

//对应的字节码
public final class basic.jvmkotlin.ForCircle {
  public final void testFor();
    Code:
     //0-7 将100 和  1 入栈,分别调用 downto 和 step函数 返回IntProgression
       0: bipush        100
       2: iconst_1
       3: invokestatic  #12   // Method kotlin/ranges/RangesKt.downTo:(II)Lkotlin/ranges/IntProgression;
       6: iconst_3
       7: invokestatic  #16   // Method kotlin/ranges/RangesKt.step:(Lkotlin/ranges/IntProgression;I)Lkotlin/ranges/IntProgression;
    //  10 -22 调用IntProgression 的 getFirst 和 getLast 和 getStep 分别存入局部变量表 1,2,3
      10: dup
      11: dup
      12: invokevirtual #22   // Method kotlin/ranges/IntProgression.getFirst:()I
      15: istore_1
      16: invokevirtual #25  // Method kotlin/ranges/IntProgression.getLast:()I
      19: istore_2
      20: invokevirtual #28  // Method kotlin/ranges/IntProgression.getStep:()I
      23: istore_3
      // 取出局部变量表 1,2,3 位置的值
      24: iload_1
      25: iload_2
      26: iload_3
      27: iflt          36     // step > 0 跳转至 36行  
      30: if_icmpgt     51     // first < last 跳转 至 51行
      33: goto          39     // 跳转至 39行
      36: if_icmplt     51     // step > 0 且   first > last  跳转至 51行
      39: iload_1       //加载first
      40: iload_2       //加载last
      41: if_icmpeq     51  //如果相等 跳转至 51行
      44: iload_1      //将 first 和step 相加 放入 first
      45: iload_3
      46: iadd
      47: istore_1
      48: goto          39  //重新执行 39行
      51: return
}

//对应的java 代码逻辑:
 public final void testFor() {
      IntProgression var10000 = RangesKt.step(RangesKt.downTo(100, 1), 3);
      int first = var10000.getFirst();
      int last= var10000.getLast();
      int step = var10000.getStep();
      if (step >= 0) {
         if (first > last) {
            return;
         }
      } else if (first < last) {
         return;
      }

      while(first != last) {
         first += step;
      }
   }

可以看到 编译器帮我们确认了 初始值和结束值,并且退出循环的条件是 first 和last 是否相等,并不是 first <= last 或者 first > last。


七、lateinit 和 by lazy

//kotlin lateinit
class LateInitTest {
    lateinit  var testData:TestData
}

//反编译的java 代码  实现了 testData 的 set 和  get 方法
public final class LateInitTest {
   @NotNull
   public TestData testData;

   @NotNull
   public final TestData getTestData() {
      TestData var10000 = this.testData;
      //判断testdata 是否为null 
      if (var10000 == null) {
         Intrinsics.throwUninitializedPropertyAccessException("testData");
      }

      return var10000;
   }

   public final void setTestData(@NotNull TestData var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.testData = var1;
   }
}

可以看到 lateinit 避免了空安全检查,告诉编译器你稍后一定会初始化,但在未初始化前使用一样会报错

实际上每次使用时调用的是 getXXX 方法,如果为空,则会抛出未初始化的异常.

class ByLazyTest {
    val testData : TestData by lazy {
        TestData("1")
    }
}
//字节码
public final class basic.jvmkotlin.ByLazyTest {
  
  //getTestData 方法
  public final basic.jvmkotlin.TestData getTestData();
    Code:
       //调用 testData$delegate 的 getValue 函数
       0: aload_0                           
       1: getfield      #11  // Field testData$delegate:Lkotlin/Lazy;
       4: astore_1           
       5: aload_0
       6: astore_2
       7: aconst_null
       8: astore_3
       9: iconst_0
      10: istore        4
      12: aload_1
      13: invokeinterface #17,  1           // InterfaceMethod kotlin/Lazy.getValue:()Ljava/lang/Object;
      18: checkcast     #19                 // class basic/jvmkotlin/TestData
      21: areturn

  //构造方法 为 初始化 testData$delegate 对象 
  public basic.jvmkotlin.ByLazyTest();
    Code:
       0: aload_0
       1: invokespecial #25                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: getstatic     #31                 // Field basic/jvmkotlin/ByLazyTest$testData$2.INSTANCE:Lbasic/jvmkotlin/ByLazyTest$testData$2;
       8: checkcast     #33                 // class kotlin/jvm/functions/Function0
      11: invokestatic  #39                 // Method kotlin/LazyKt.lazy:(Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;
      14: putfield      #11                 // Field testData$delegate:Lkotlin/Lazy;
      17: return
}


//java 代码
public final class ByLazyTest {
   @NotNull
   private final Lazy testData$delegate;

   @NotNull
   public final TestData getTestData() {
      Lazy var1 = this.testData$delegate;
      Object var3 = null;
      boolean var4 = false;
      return (TestData)var1.getValue();
   }

   public ByLazyTest() {
      this.testData$delegate = LazyKt.lazy((Function0)null.INSTANCE);
   }
}
override val value: T
    get() {
        val _v1 = _value
        if (_v1 !== UNINITIALIZED_VALUE) {
            @Suppress("UNCHECKED_CAST")
            return _v1 as T
        }

        return synchronized(lock) {
            val _v2 = _value
            if (_v2 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST") (_v2 as T)
            } else {
                val typedValue = initializer!!()
                _value = typedValue
                initializer = null
                typedValue
            }
        }
    }

小结

构造函数 调用 了 LazyKt.lazy方法,给变量 testData$delegate 赋值,而第一次获取getTestData 时 会调用 lazy的 getValue 方法,而getvalue的实现细节类似于单例,如果已经有值,则直接返回,否则初始化后返回。


八、总结

本文从字节码层面看kotlin 顶层函数、扩展函数、单例、默认属性、for循环、lateinit和 bylazy 的实现原理。

If it is helpful for you ,please give me a Like !!!!!