Kotlin字节码层探究构造函数与成员变量和init代码块执行顺序

接下来我将为你详细讲解 Kotlin 字节码层探究构造函数、成员变量和 init 代码块执行顺序的攻略。

背景

在 Kotlin 中,成员变量和 init 代码块是可以在类中定义的,而它们的执行顺序和构造函数有着密切的关系。在了解 Kotlin 字节码层探究构造函数、成员变量和 init 代码块执行顺序之前,我们先来回顾一下 Kotlin 中的构造函数。

Kotlin 中的构造函数

Kotlin 中的构造函数分为主构造函数和次构造函数。主构造函数是与类名一起声明的一部分,可以包含属性初始化代码和它们的默认值。次构造函数则必须委托给主构造函数或者其他次构造函数。

成员变量和 init 代码块

除构造函数之外,Kotlin 中的类还可以定义成员变量和 init 代码块。成员变量和 init 代码块的执行顺序与对象初始化有关,而对象初始化则与构造函数有直接的关系。因此,了解构造函数、成员变量和 init 代码块之间的执行顺序是非常重要的。

字节码层探究

在字节码层面上,Kotlin 中的对象初始化主要涉及到两个指令:invokespecialinvokevirtual。其中,invokespecial 指令用于调用构造函数,而 invokevirtual 指令用于调用 init 代码块。

在下面的内容中,我们将通过如何使用 Javap 工具、反编译 Kotlin 代码以及分析字节码指令的方式来探究构造函数、成员变量和 init 代码块之间的执行顺序。

示例1

首先,我们来看下面这个 Kotlin 类的代码:

class MyClass(var i: Int = 0) {
    val j: Int = i * 2
    init {
        print("init: $i")
    }
}

fun main() {
    val myClass = MyClass(3)
}

这个类包含一个主构造函数和一个 init 代码块。同时,类中还定义了一个成员变量 j,该成员变量的值是通过 i 计算得出的。

接下来,我们使用 Javap 工具查看字节码。在 Kotlin 中,使用 Javap 查看字节码需要使用针对 Kotlin 的 Javap 版本,例如 kotlinc -d . MyClass.kt 生成 MyClass.class 后,使用 javap -p -v MyClassKt 查看 MyClassKt 中的字节码。我们查看的是 init 方法,因为它和成员变量的赋值都是在构造函数执行之前的:

init<init>()V
   L0
    LINENUMBER 5 L0
    ALOAD 0
    INVOKESPECIAL kotlin/jvm/internal/DefaultConstructorMarker.<init> ()V
   L1
    ALOAD 0
    ILOAD 1
    PUTFIELD MyClass.i : I
   L2
    ALOAD 0
    ALOAD 0
    GETFIELD MyClass.i : I
    ICONST_2
    IMUL
    PUTFIELD MyClass.j : I
   L3
    ALOAD 0
    LDC "init: "
    INVOKESTATIC kotlin/io/ConsoleKt.access$print(Ljava/lang/Object;Ljava/lang/Object;)V
   L4
    ALOAD 0
    GETFIELD MyClass.i : I
    INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;
    LDC ""
    INVOKESTATIC kotlin/text/StringsKt__StringBuilderKt.append(Ljava/lang/StringBuilder;Ljava/lang/Object;)Ljava/lang/StringBuilder;
    LDC ""
    INVOKESTATIC kotlin/text/StringsKt__StringBuilderKt.append(Ljava/lang/StringBuilder;Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "init: "
    INVOKEVIRTUAL kotlin/text/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
   L5
    ALOAD 0
    GETFIELD MyClass.i : I
    INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;
    LDC ""
    INVOKESTATIC kotlin/text/StringsKt__StringBuilderKt.append(Ljava/lang/StringBuilder;Ljava/lang/Object;)Ljava/lang/StringBuilder;
   L6
    POP
   L7
    RETURN
   L8
    LOCALVARIABLE this LMyClassKt; L0 L8 0
    MAXSTACK = 3
    MAXLOCALS = 2

从上面的字节码中可以看到,invokespecial 指令调用了 kotlin/jvm/internal/DefaultConstructorMarker.<init>()V 方法,这是因为 Kotlin 中的类的构造函数会默认调用该方法。紧接着,ALOADPUTFIELD 指令分别将传入的参数 i 赋值到类的 i 属性上。然后,ALOADGETFIELDICONST_2IMUL 指令分别计算并将结果赋值给类的 j 属性上。最后,在 init 代码块中,我们可以看到打印了 init:i,说明 init 代码块已经被执行。由于类中的成员变量 j 和 init 代码块的执行顺序 是和构造函数的调用顺序没有关系,所以 j 成员变量此时已被成功计算出来。

在 main 函数中,我们创建了一个 MyClass 的实例,并将参数传递给构造函数。由于我们没有定义次构造函数,所以实际上调用了 主构造函数。在构造实例时,首先会执行 构造函数,然后才会执行对象的 j 成员变量的赋值。最后,执行 init 代码块。因此,在 main 函数中,我们能够看到输出值为:init: 3

示例2

接下来,我们再看一个场景,该场景下我们定义两个 init 代码块:

class MyClass {
    var i: Int = 0

    init {
        println("First init block i = $i")
    }

    constructor(i: Int) : this() {
        this.i = i
    }

    init {
        println("Second init block i = $i")
    }
}

fun main() {
    MyClass(42)
}

在这个类中,我们定义了两个 init 代码块。这两个代码块都有对 i 实例变量的引用,因此我们满足了条件:init 代码块为不同的变量赋值,而且它们的执行次序之间的关系应该是有一定的依赖关系。接下来,使用 Javap 查看字节码文件:

<init>()V
   L0
    LINENUMBER 5 L0
    ALOAD 0
    INVOKESPECIAL kotlin/jvm/internal/DefaultConstructorMarker.<init> ()V
   L1
    ALOAD 0
    ICONST_0
    PUTFIELD MyClass.i : I
   L2
    ALOAD 0
    LDC "First init block i = "
    INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
   L3
    ALOAD 0
    GETFIELD MyClass.i : I
    INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;
    LDC ""
    INVOKESTATIC kotlin/text/StringsKt__StringBuilderKt.append(Ljava/lang/StringBuilder;Ljava/lang/Object;)Ljava/lang/StringBuilder;
   L4
    ALOAD 0
    GETFIELD MyClass.i : I
    INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;
    LDC ""
    INVOKESTATIC kotlin/text/StringsKt__StringBuilderKt.append(Ljava/lang/StringBuilder;Ljava/lang/String;)Ljava/lang/StringBuilder;
   L5
    ALOAD 0
    LDC "First init block i = "
    INVOKEVIRTUAL kotlin/text/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
   L6
    ALOAD 0
    GETFIELD MyClass.i : I
    INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;
    LDC ""
    INVOKESTATIC kotlin/text/StringsKt__StringBuilderKt.append(Ljava/lang/StringBuilder;Ljava/lang/Object;)Ljava/lang/StringBuilder;
   L7
    INVOKEVIRTUAL kotlin/text/StringBuilder.toString ()Ljava/lang/String;
   L8
    INVOKESTATIC kotlin/io/ConsoleKt.println(Ljava/lang/String;)V
   L9
    RETURN
   L10
    LOCALVARIABLE this LMyClass; L0 L10 0
    MAXSTACK = 5
    MAXLOCALS = 1

<init>(I)V
   L0
    LINENUMBER 8 L0
    ALOAD 0
    INVOKESPECIAL MyClass.<init> ()V
   L1
    ALOAD 0
    ILOAD 1
    PUTFIELD MyClass.i : I
   L2
    ALOAD 0
    LDC "Second init block i = "
    INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V
   L3
    ALOAD 0
    GETFIELD MyClass.i : I
    INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;
    LDC ""
    INVOKESTATIC kotlin/text/StringsKt__StringBuilderKt.append(Ljava/lang/StringBuilder;Ljava/lang/Object;)Ljava/lang/StringBuilder;
   L4
    ALOAD 0
    GETFIELD MyClass.i : I
    INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;
    LDC ""
    INVOKESTATIC kotlin/text/StringsKt__StringBuilderKt.append(Ljava/lang/StringBuilder;Ljava/lang/String;)Ljava/lang/StringBuilder;
   L5
    ALOAD 0
    LDC "Second init block i = "
    INVOKEVIRTUAL kotlin/text/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
   L6
    ALOAD 0
    GETFIELD MyClass.i : I
    INVOKESTATIC java/lang/Integer.valueOf(I)Ljava/lang/Integer;
    LDC ""
    INVOKESTATIC kotlin/text/StringsKt__StringBuilderKt.append(Ljava/lang/StringBuilder;Ljava/lang/Object;)Ljava/lang/StringBuilder;
   L7
    INVOKEVIRTUAL kotlin/text/StringBuilder.toString ()Ljava/lang/String;
   L8
    INVOKESTATIC kotlin/io/ConsoleKt.println(Ljava/lang/String;)V
   L9
    RETURN
   L10
    LOCALVARIABLE this LMyClass; L0 L10 0
    LOCALVARIABLE i I L0 L10 1
    MAXSTACK = 5
    MAXLOCALS = 2

在上面的字节码中,我们可以看到,先是调用了 kotlin/jvm/internal/DefaultConstructorMarker.<init>()V 方法,然后初始化 i 的值,接着执行当中的第一个 init 代码块。最后,执行的是我们传入的 42 值赋值的构造方法中的第二个 init 代码块。所以,最后的输出结果为:

First init block i = 0
Second init block i = 42

总结

通过这两个示例,我们了解了 Kotlin 字节码层探究构造函数与成员变量和 init 代码块执行顺序。成员变量和 init 代码块的执行顺序与对象初始化有关,而对象初始化则与构造函数有直接的关系。在使用 Javap 工具查看字节码时,我们可以通过查看调用的指令来判断哪个代码块先执行,哪个代码块后执行。掌握了这些知识后,我们可以更好地理解 Kotlin 中的对象初始化过程。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Kotlin字节码层探究构造函数与成员变量和init代码块执行顺序 - Python技术站

(0)
上一篇 2023年6月26日
下一篇 2023年6月26日

相关文章

  • 小程序组件之自定义顶部导航实例

    小程序组件之自定义顶部导航实例 概述 在小程序中,我们可以通过wx.showNavigationBarLoading()和wx.hideNavigationBarLoading()等系列API来控制顶部导航条的显示和隐藏,但是如果希望自定义顶部导航,那么可以使用wx.setNavigationBarColor()API来设置顶部导航的颜色、背景色和文字内容等…

    other 2023年6月25日
    00
  • Android批量修改文件格式/文件名的神操作分享

    下面就是详细讲解“Android批量修改文件格式/文件名的神操作分享”的完整攻略。 如何批量修改文件格式 步骤一:下载文件格式转换工具 首先,在Android手机上下载并安装一款文件格式转换工具,比如”Format Factory”或”Any Video Converter”等。 步骤二:打开文件格式转换工具 打开下载好的工具,并点击”格式转换”或相应的按钮…

    other 2023年6月26日
    00
  • centos7.2基础安装和配置(含分区方案建议)

    CentOS7.2基础安装和配置(含分区方案建议) 本文将介绍在CentOS 7.2上进行基础安装和配置的步骤,以及分区方案建议。我们将使用图形化安装界面进行安装,同时还将介绍一些必要的安全措施和配置优化。 准备工作 在开始安装之前,我们需要准备一些必要的工具和文件: CentOS 7.2 ISO镜像文件 一张空白DVD或可启动的USB闪存盘 一台计算机 安…

    其他 2023年3月28日
    00
  • 批处理入门手册之批处理常用DOS命令篇(echo、rem、cd、dir)

    批处理入门手册之批处理常用DOS命令篇 介绍 本篇攻略将会介绍批处理中常用的DOS命令,包括echo、rem、cd、dir,这些命令在日常批处理中使用频率较高,掌握这些命令将能够提高批处理的效率。 echo命令 echo命令用于在批处理执行过程中输出文本信息,其基本语法如下: echo 输出的文本 例如,在批处理脚本中使用echo命令输出“Hello Wor…

    other 2023年6月26日
    00
  • Word2016中visio图像右键不能打开怎么办?

    如果 Word 2016 中 Visio 图像右键不能打开,可能是由于安装问题或配置设置问题导致的。下面提供一些可能有用的方法,帮助解决这个问题。 方法一:检查 Visio 安装 首先,需要确保 Visio 已经正确安装。如果安装过程中出现错误或问题,可能导致 Visio 图像在 Word 中无法打开。可以按照以下步骤检查 Visio 的安装情况。 打开“控…

    other 2023年6月27日
    00
  • 关于php中一些字符串总结

    关于PHP中一些字符串的总结 在PHP中,字符串处理不可避免,了解一些字符串相关的函数和技巧可以提高编码效率。下面是一些关于PHP中字符串的总结。 字符串的基本操作 字符串的拼接 字符串的拼接可以使用.操作符或$a .= $b的方式来实现。例如: $a = "Hello"; $b = "World"; echo $a …

    other 2023年6月20日
    00
  • iOS中输入框设置指定字符输入的方法

    Sure! 下面是关于在iOS中设置指定字符输入的方法的完整攻略,包含两个示例说明。 方法一:使用代理方法 创建一个遵循UITextFieldDelegate协议的类,并将其设置为输入框的代理对象。 class MyTextFieldDelegate: NSObject, UITextFieldDelegate { func textField(_ text…

    other 2023年8月18日
    00
  • 浅谈Android性能优化之内存优化

    浅谈Android性能优化之内存优化 1. 优化内存的重要性 在Android应用开发中,内存优化是提高应用性能和用户体验的关键因素之一。优化内存可以减少应用的内存占用,提高应用的响应速度和稳定性,减少崩溃和ANR(Application Not Responding)的发生频率。 2. 内存优化的常见手段 2.1. 减少内存泄漏 内存泄漏是指应用中已经不再…

    other 2023年7月31日
    00
合作推广
合作推广
分享本页
返回顶部