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日

相关文章

  • 怎么免费激活CodeLobster IDE 附激活教程+注册机

    怎么免费激活CodeLobster IDE 附激活教程+注册机 1. 什么是 CodeLobster IDE CodeLobster IDE 是一款功能全面且易于使用的集成开发环境。它支持多种编程语言,如 PHP、JavaScript、HTML、CSS、SQL 等。CodeLobster IDE 的特点包括:语法高亮、自动完成、调试器、版本控制、数据库管理器…

    other 2023年6月26日
    00
  • 设备像素比devicepixelratio简单介绍

    设备像素比devicePixelRatio简单介绍 设备像素比(devicePixelRatio)定义了浏览器在渲染网页时使用的物理像素和CSS像素之间的比例。从概念上讲,设备像素比是一个浏览器显示器和一个真实显示设备之间的比例。例如,如果一个设备的屏幕的物理分辨率为1920×1080,设备像素比为2,那么浏览器将渲染CSS像素使得该分辨率如同960×540…

    其他 2023年3月29日
    00
  • tokudb的特点验证

    tokudb的特点验证 Tokudb是一个高性能、节省空间的MySQL存储引擎,它采用了特别的技术,包括 Fractal Tree 索引、Hot Column Cache、无限扩展等等。那么,如何验证Tokudb这些特点呢? Fractal Tree 索引 Tokudb的 Fractal Tree 索引是其最大的特点之一,它可以在索引中支持无限个条目。这就是…

    其他 2023年3月28日
    00
  • 使用Spring初始化加载InitializingBean()方法

    使用Spring初始化加载InitializingBean()方法是Spring框架提供的一个扩展点,该方法用于在Bean对象的初始化之后,执行一些初始化操作。初始化操作通常指一些依赖注入、参数检验、资源加载、连接池初始化等一些预备工作,使得应用程序在正式工作之前,尽可能地完成一些准备工作,达到最优的性能表现和可靠性要求。 使用Spring初始化加载Init…

    other 2023年6月20日
    00
  • 使用华为云鲲鹏弹性云服务器部署Discuz的详细过程

    使用华为云鲲鹏弹性云服务器部署Discuz的过程可以分为以下几步: 创建鲲鹏弹性云服务器 配置服务器环境 安装与配置MySQL 下载与配置Discuz 安装与配置nginx 配置防火墙 下面详细介绍每一步的具体操作过程: 创建鲲鹏弹性云服务器 在华为云上创建鲲鹏弹性云服务器的过程可以参考官方文档:https://support.huaweicloud.com…

    other 2023年6月26日
    00
  • 详解JS构造函数中this和return

    接下来我会详细讲解 JavaScript 构造函数中 this 和 return 的相关内容。 什么是构造函数 在 JavaScript 中,构造函数是用来创建对象的函数,被调用时会返回一个新的对象。通常使用 new 关键字来调用构造函数。 以下是一个简单的构造函数示例: function Person(name, age) { this.name = na…

    other 2023年6月26日
    00
  • 如何利用Java使用AOP实现数据字典转换

    当使用Java编程语言时,可以利用AOP(面向切面编程)的概念来实现数据字典转换。下面是一个完整的攻略,包含两个示例说明: 1. 引入依赖 首先,需要在项目的构建文件(如pom.xml)中引入AOP相关的依赖,例如Spring AOP或AspectJ。 <dependency> <groupId>org.springframework…

    other 2023年10月18日
    00
  • PS如何自定义画笔?PS定义画笔预设方法介绍

    PS是一款功能强大的图形处理软件,不仅拥有各种常规的画笔工具,还可以自定义画笔。下面是自定义画笔的详细攻略: 一、自定义画笔方法 1. 打开画笔编辑器 在PS软件中打开画笔编辑器,方法是在工具栏中找到画笔工具,右键单击选择“画笔预设”,在下拉菜单中选择“画笔编辑器”。 2. 新建一个画笔 在画笔编辑器界面中,点击下方的“新建画笔”按钮。然后选择基础画笔,可以…

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