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

之前写了一篇文章,从Java语法的角度分析了Kotlin构造函数、成员变量初始化、init代码块三者的执行顺序:

Kotlin构造函数与成员变量和init代码块执行顺序详细讲解

这次再从字节码的角度分析它们的执行顺序。

还是用之前那个例子:

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)
    init {
        println("First initializer block that prints ${name}")
    }
    val secondProperty = "Second property: ${name.length}".also(::println)
    init {
        println("Second initializer block that prints ${name.length}")
    }
}

调用InitOrderDemo(“hello”)打印的结果如下:

First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5

可以看到执行顺序,是按照它们声明的顺序执行。

将上面Koltin代码转成字节码之后,显示内容如下:

// ================com/devnn/javalib/InitOrderDemo.class =================
// class version 52.0 (52)
// access flags 0x31
public final class com/devnn/javalib/InitOrderDemo {
  // access flags 0x12
  private final Ljava/lang/String; firstProperty
  @Lorg/jetbrains/annotations/NotNull;() // invisible
  // access flags 0x11
  public final getFirstProperty()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 4 L0
    ALOAD 0
    GETFIELD com/devnn/javalib/InitOrderDemo.firstProperty : Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/devnn/javalib/InitOrderDemo; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
  // access flags 0x12
  private final Ljava/lang/String; secondProperty
  @Lorg/jetbrains/annotations/NotNull;() // invisible
  // access flags 0x11
  public final getSecondProperty()Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 10 L0
    ALOAD 0
    GETFIELD com/devnn/javalib/InitOrderDemo.secondProperty : Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/devnn/javalib/InitOrderDemo; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
  // access flags 0x1
  public <init>(Ljava/lang/String;)V
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 1
    LDC "name"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 3 L1
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L2
    LINENUMBER 4 L2
    ALOAD 0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "First property: "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 2
   L3
    ALOAD 2
    ASTORE 3
   L4
    LINENUMBER 17 L4
    ASTORE 5
   L5
    ICONST_0
    ISTORE 4
   L6
    LINENUMBER 4 L6
   L7
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 3
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L8
   L9
   L10
    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
    ASTORE 6
    ALOAD 5
   L11
    LINENUMBER 4 L11
   L12
    ALOAD 2
   L13
    PUTFIELD com/devnn/javalib/InitOrderDemo.firstProperty : Ljava/lang/String;
   L14
    LINENUMBER 6 L14
    NOP
   L15
    LINENUMBER 7 L15
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "First initializer block that prints "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 2
   L16
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L17
   L18
    LINENUMBER 8 L18
    NOP
   L19
    LINENUMBER 10 L19
    ALOAD 0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "Second property: "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/String.length ()I
    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 2
   L20
    ALOAD 2
    ASTORE 3
   L21
    LINENUMBER 17 L21
    ASTORE 5
   L22
    ICONST_0
    ISTORE 4
   L23
    LINENUMBER 10 L23
   L24
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 3
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L25
   L26
   L27
    GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
    ASTORE 6
    ALOAD 5
   L28
    LINENUMBER 10 L28
   L29
    ALOAD 2
   L30
    PUTFIELD com/devnn/javalib/InitOrderDemo.secondProperty : Ljava/lang/String;
   L31
    LINENUMBER 12 L31
    NOP
   L32
    LINENUMBER 13 L32
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "Second initializer block that prints "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/String.length ()I
    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 2
   L33
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L34
   L35
    LINENUMBER 14 L35
    RETURN
   L36
    LOCALVARIABLE p1 Ljava/lang/Object; L5 L10 3
    LOCALVARIABLE $i$a$-unknown-InitOrderDemo$firstProperty$1 I L6 L10 4
    LOCALVARIABLE p1 Ljava/lang/Object; L22 L27 3
    LOCALVARIABLE $i$a$-unknown-InitOrderDemo$secondProperty$1 I L23 L27 4
    LOCALVARIABLE this Lcom/devnn/javalib/InitOrderDemo; L0 L36 0
    LOCALVARIABLE name Ljava/lang/String; L0 L36 1
    MAXSTACK = 3
    MAXLOCALS = 7
}

可以看到上面的构造函数、成员变量初始化和init代码块,按照声明都被放到了字节码的init代码块中了。

字节码的init初始化器其实就是类的构造函数。将Java代码转成字节码也是存在init构造函数。

下面看一个Java示例,加深对字节码的init初始化块的认识。

package com.devnn.javalib;
public class JavaInit {
    String firstName = "Steven";
    {
        System.out.println("This is init block");
    }
    JavaInit(String secondName) {
        System.out.println("firstName=" + firstName);
        System.out.println("secondName=" + secondName);
    }
    public static void main(String[] args) {
        new JavaInit("Jobs");
    }
}

运行main函数打印结果如下:

This is init block
firstName=Steven
secondName=Jobs

将上面的JavaInit类转成字节码之后的内容如下:

// class version 51.0 (51)
// access flags 0x21
public class com/devnn/javalib/JavaInit {
  // compiled from: JavaInit.java
  // access flags 0x0
  Ljava/lang/String; firstName
  // access flags 0x0
  <init>(Ljava/lang/String;)V
   L0
    LINENUMBER 10 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 4 L1
    ALOAD 0
    LDC "Steven"
    PUTFIELD com/devnn/javalib/JavaInit.firstName : Ljava/lang/String;
   L2
    LINENUMBER 7 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "This is init block"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L3
    LINENUMBER 11 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "firstName="
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    GETFIELD com/devnn/javalib/JavaInit.firstName : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L4
    LINENUMBER 12 L4
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "secondName="
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L5
    LINENUMBER 13 L5
    RETURN
   L6
    LOCALVARIABLE this Lcom/devnn/javalib/JavaInit; L0 L6 0
    LOCALVARIABLE secondName Ljava/lang/String; L0 L6 1
    MAXSTACK = 3
    MAXLOCALS = 2
  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 16 L0
    NEW com/devnn/javalib/JavaInit
    DUP
    LDC "Jobs"
    INVOKESPECIAL com/devnn/javalib/JavaInit.<init> (Ljava/lang/String;)V
    POP
   L1
    LINENUMBER 17 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 3
    MAXLOCALS = 1
}

可见,Java类的成员变量初始化、构造函数、构造块同样都被拷贝进了init代码块中。那么它们是否存在顺序问题呢?

将上面JavaInit类的firname成员变量放到初始化块下面试试:

package com.devnn.javalib;
public class JavaInit {
    {
        System.out.println("This is init block");
    }
    JavaInit(String secondName) {
        System.out.println("firstName=" + firstName);
        System.out.println("secondName=" + secondName);
    }
    String firstName = "Steven";
    public static void main(String[] args) {
        new JavaInit("Jobs");
    }
}

查看字节码:

// class version 51.0 (51)
// access flags 0x21
public class com/devnn/javalib/JavaInit {
  // compiled from: JavaInit.java
  // access flags 0x0
  Ljava/lang/String; firstName
  // access flags 0x0
  <init>(Ljava/lang/String;)V
   L0
    LINENUMBER 8 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 5 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "This is init block"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L2
    LINENUMBER 13 L2
    ALOAD 0
    LDC "Steven"
    PUTFIELD com/devnn/javalib/JavaInit.firstName : Ljava/lang/String;
   L3
    LINENUMBER 9 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "firstName="
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    GETFIELD com/devnn/javalib/JavaInit.firstName : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L4
    LINENUMBER 10 L4
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "secondName="
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L5
    LINENUMBER 11 L5
    RETURN
   L6
    LOCALVARIABLE this Lcom/devnn/javalib/JavaInit; L0 L6 0
    LOCALVARIABLE secondName Ljava/lang/String; L0 L6 1
    MAXSTACK = 3
    MAXLOCALS = 2
  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 17 L0
    NEW com/devnn/javalib/JavaInit
    DUP
    LDC "Jobs"
    INVOKESPECIAL com/devnn/javalib/JavaInit.<init> (Ljava/lang/String;)V
    POP
   L1
    LINENUMBER 18 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 3
    MAXLOCALS = 1
}

可见,Java类的成员变量初始化、构造函数、构造块同样都被拷贝进了字节码init代码块中。Java的成员变量初始化和构造块也是按声明顺序执行。不同的是,Java的构造函数代码始终放在了字节码init代码块的后面。

字节码的init初始化块,其实就是类的真正的构造函数。Kotlin多个init代码块都是按照顺序拷贝进了字节码的init初始化块中,可以理解为它们是构造函数的组成部分。

Java和kotlin成员变量的初始化都是放到字节码的init代码块中,也就是在构造函数中执行的。

到此这篇关于Kotlin字节码层探究构造函数与成员变量和init代码块执行顺序的文章就介绍到这了,更多相关Kotlin构造函数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Kotlin如何安全访问lateinit变量的实现

    Kotlin设计之初就是不允许非null变量在声明期间不进行初始化的,为了解决这个问题,Kotlin lateinit 允许我们先声明一个变量,然后在程序执行周期的将来某个时候将其初始化,让编译检查时不会 因为属性变量未被初始化而报错.如果未初始化将导致以下异常: kotlin.UninitializedPropertyAccessException: lateinit property mList has not been initialized 所以我们在 Kotlin 1.2及更高版本上,

  • Kotlin lateinit与by lazy案例详解

    lateinit 和 lazy 是 Kotlin 中的两种不同的延迟初始化的实现 lateinit 只用于变量 var,而 lazy 只用于常量 val lazy 应用于单例模式(if-null-then-init-else-return),而且当且仅当变量被第一次调用的时候,委托方法才会执行. lazy()是接受一个 lambda 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并

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

    之前写了一篇文章,从Java语法的角度分析了Kotlin构造函数.成员变量初始化.init代码块三者的执行顺序: Kotlin构造函数与成员变量和init代码块执行顺序详细讲解 这次再从字节码的角度分析它们的执行顺序. 还是用之前那个例子: class InitOrderDemo(name: String) { val firstProperty = "First property: $name".also(::println) init { println("First i

  • Kotlin构造函数与成员变量和init代码块执行顺序详细讲解

    目录 在Kotlin中经常看到主构造函数.成员变量.init代码块(也叫初始化器),它们的执行时机和顺序是什么样的呢?看一下官方的示例: class InitOrderDemo(name: String) { val firstProperty = "First property: $name".also(::println) init { println("First initializer block that prints ${name}") } val se

  • 一篇文章带你从java字节码层理解i++和++i

    目录 程序目的 关键指令 i++示例源码 使用jclasslib查看i++字节码 ++i示例源码 参考 总结 程序目的 从java字节码层理解,为何i = i++后,结果是+1之前的数值.而i=++i后,结果是+1之后的值. 关键指令 iload_<n>:从局部变量表获取值,并压入操作数栈. istore_<n>:出栈,然后存储到局部变量表. i++示例源码 public class TestIPulsPlus { public static void main(String[]

  • react源码层探究setState作用

    目录 前言 为什么setState看起来是异步的 从first paint开始 触发组件更新 更新渲染fiber tree 写在最后 前言 在深究 React 的 setState 原理的时候,我们先要考虑一个问题:setState 是异步的吗? 首先以 class component 为例,请看下述代码(demo-0) class App extends React.Component { state = { count: 0 } handleCountClick = () => { this

  • C#构造函数在基类和父类中的执行顺序

    一.简介 当我们没有在子类构造函数中写上 base(),默认会先调用父类中无参的构造函数,再调用子类.当在有参构造函数后写上base时,只调用子类构造函数中有参的构造函数,隐藏父类无参构造函数. 二.代码案例 父类代码: #region 父类 /// <summary> /// 测试构造函数--需要被构造类 Fu /// </summary> class Fu { private int x;//一个简单的私有字段 /// <summary> /// 构造函数 ///

  • Kotlin by lazy关键字深入探究实现原理

    目录 前言 ViewModel和ViewBinding变量初始化过程 by lazy关键字的字节码实现 by lazy关键字的Java实现 前言 kotlin的by lazy关键字是很常用的,它表示延时初始化变量,只在第一次使用时才给它初始化.那么它是如何实现这种功能的呢?这篇文章从字节码和Java语言的角度揭密它的实现原理. ViewModel和ViewBinding变量初始化过程 先举两个项目中最常见的例子:ViewModel和ViewBinding,了解一下为什么需要延时初始化. 看一段代

  • Python使用dis模块把Python反编译为字节码的用法详解

    dis - Disassembler for Python bytecode,即把python代码反汇编为字节码指令. 使用超级简单: python -m dis xxx.py Python 代码是先被编译为字节码后,再由Python虚拟机来执行字节码, Python的字节码是一种类似汇编指令的中间语言, 一个Python语句会对应若干字节码指令,虚拟机一条一条执行字节码指令, 从而完成程序执行. Python dis 模块支持对Python代码进行反汇编, 生成字节码指令. 当我在网上看到wh

  • 通过java字节码分析学习对象初始化顺序

    复制代码 代码如下: mockery.checking(new Expectations() { {               one(new Object()).toString();               will(returnValue(""));           }       }); 下面写一个写一个简单的类演示这个例子 复制代码 代码如下: public class Test { int i = 1;    {        int j = 1;       

  • C#不同类型的成员变量(字段)的默认值介绍

    创建类的一个实例时,在执行构造函数之前,如果你没有给成员变量赋初始值,C#编译器缺省将每一个成员变量初始化为他的默认值. 如果变量是方法的局部变量,编译器就会认为在使用该变量之前,代码必须给它显示的设定一个值.否则会发生"使用了未赋值的局部变量"的错误. 对于其他情况,编译器会在创建变量时,把变量初始化为默认值.1.对于整型.浮点型.枚举类型(数值型),默认值为0或0.0.2.字符类型的默认值为\x0000.3.布尔类型的默认值为false.4.引用类型的默认值为null. 如果声时变

  • PHP代码优化之成员变量获取速度对比

    有如下4个代码示例,你认为他们创建对象,并且获得成员变量的速度排序是怎样的? 1:将成员变量设置为public,通过赋值操作给成员变量赋值,直接获取变量 复制代码 代码如下: <?phpclass Foo {    public $id;}$data = new Foo;$data->id = 10;echo $data->id;?> 2:将成员变量设置为public,通过构造函数设置成员变量的值,直接获取变量 复制代码 代码如下: <?phpclass Foo2 { pub

随机推荐