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

yizhihongxing

接下来我将为你详细讲解 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日

相关文章

  • 共享内存简介和mmap 函数

    共享内存简介和mmap 函数的完整攻略 共享内存简介 共享内存是一种进程间通信的方式,它允许多个进程访问同一块物理内存,从而实现数据共享。与其他进程间通信方式相比,共享内存具有高效、灵活、易用等优点。 在Linux系统中,共享内存是通过shmget、shmat、shmdt、shmctl等系统调用来实现的。其中,shmget用于创建或获取共享内存标识符,shm…

    other 2023年5月5日
    00
  • IOS百度地图导航开发功能实现简述

    IOS百度地图导航开发功能实现简述 概述 百度地图提供了IOS版本的SDK,可以方便地在IOS设备上进行地图导航功能的开发。本文将针对IOS百度地图导航功能进行简述,包含以下几个方面的内容: 获取apiKey。 导入百度地图SDK。 初始化MapView和LocationService。 设置MapView的缩放比例、地图类型、显示模式等。 在MapView…

    other 2023年6月26日
    00
  • powerbi基础操作-summarizecolumns()函数

    Power BI基础操作 – summarizecolumns()函数 summarizecolumns()是Power BI中的一个DAX函数,用于对数据表中的列进行汇总计算。本攻略将介绍summarize()函数的用法,并提供两个示例。 语法 summarizecolumns()函数的语法如下: SUMMARIZEC ( <column1>,…

    other 2023年5月9日
    00
  • Cmd使用方式–命令行运行程序

    Cmd是Windows操作系统中的命令行工具,可以通过它来运行程序、管理文件、配置系统等。以下是“Cmd使用方式–命令行运行程序”的完整攻略: 命令行运行程序的基本语法 在Cmd中,可以使用以下语法来运行程序: 程序路径 [参数1] [参数2] … 其中,程序路径是要运行的程序的路径,参数1、参数2等是程序的参数。例如,要运行一个名为hello.exe…

    other 2023年5月5日
    00
  • Linux下sshd服务及服务管理命令详解

    Linux下sshd服务及服务管理命令详解 什么是sshd服务 sshd是Secure Shell(安全外壳协议)的服务端程序,可以提供安全的远程登录主机。sshd在Unix系统和Linux系统中都有提供,一般安装在服务器端,可以通过ssh客户端连接。 安装sshd服务 在Linux系统中,默认情况下并不会自动安装sshd服务,需要手动安装。以Debian/…

    other 2023年6月27日
    00
  • WCF实现的计算器功能实例

    WCF实现的计算器功能实例 1. 准备工作 安装 Visual Studio 2017 及以上版本。 确认已安装 .NET Framework 4.7.2 及以上版本。 准备一个空白的 WCF 应用程序项目。 2. 创建 WCF 服务 在项目中添加一个 WCF 服务。右键项目 -> 添加 -> 新增项 -> WCF 服务。 在 IServi…

    other 2023年6月27日
    00
  • c#中 什么时候用yieldreturn

    c#中什么时候用yield return 在C#中,使用yield return关键字可以将一个方法转化为一个迭代器。当方法返回一个迭代器时,每次调用MoveNext()方法时都会执行一部分方法,返回一个值,并在下次继续执行方法,直到所有值都被返回。在本文中,我们将讨论在何种情况下应该使用yield return。 1. 处理大量数据 当需要处理大量数据时,…

    其他 2023年3月28日
    00
  • 淘宝账号安全吗?淘宝账号进行自检安全评级的教程

    淘宝是目前中国最大的网购平台之一,因此保护淘宝账号的安全非常重要。下面将介绍淘宝账号安全性评估的方法,以及如何提高自己的账号安全性评级。 评估账号安全性 进入“我的淘宝”页面,点击“账号安全”进入安全中心; 查看“账号安全等级”中的详细信息,包括登录密码、支付密码、手机绑定、邮箱绑定、实名认证等,根据系统的评估结果来判断账号安全性; 如果显示“高级别保护”,…

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