Java中实例初始化方法()原理解析

1. 前言

  • 本文主要从字节码角度解析使用new关键字创建对象时,实例初始化方法<init>()的执行过程和原理

2. 测试代码

  • 本次实验所举的例子并不具备实际意义,只是为了实验方便而取名为Anima和Dog
class Animal{
	int number = 10;
	Animal(){
		number++;
		System.out.println(number);
	}
}


class Dog extends Animal{
	int number = getDefaultNumber();
	final int defaultNumber = 20;
	{
		System.out.println("This nonstatic block");
	}
	Dog(){
		this(20);
	}
	
	Dog(int num){
		number = num;
		System.out.println(number);
	}
	
	int getDefaultNumber(){
		return defaultNumber;
	}
}

public class InheritTest{
	public static void main(String[] args){
		new Dog();
        // Output:
        // 11
        // This nonstatic block
        // 20
	}
}

3. 反编译class文件

  • 使用javap工具反汇编class文件输出字节码命令,工具使用格式javap -c Animal.class

  • Animal.class

// Animal.class
Compiled from "InheritTest.java"
class Animal {
  int number;

  Animal();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: bipush        10
       7: putfield      #2                  // Field number:I
      10: aload_0
      11: dup
      12: getfield      #2                  // Field number:I
      15: iconst_1
      16: iadd
      17: putfield      #2                  // Field number:I
      20: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      23: aload_0
      24: getfield      #2                  // Field number:I
      27: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
      30: return
}

  • Dog.class
// Dog.class
Compiled from "InheritTest.java"
class Dog extends Animal {
  int number;

  final int defaultNumber;

  Dog();
    Code:
       0: aload_0
       1: bipush        20
       3: invokespecial #1                  // Method "<init>":(I)V
       6: return

  Dog(int);
    Code:
       0: aload_0
       1: invokespecial #2                  // Method Animal."<init>":()V
       4: aload_0
       5: aload_0
       6: invokevirtual #3                  // Method getDefaultNumber:()I
       9: putfield      #4                  // Field number:I
      12: aload_0
      13: bipush        20
      15: putfield      #5                  // Field defaultNumber:I
      18: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      21: ldc           #7                  // String This nonstatic block
      23: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      26: aload_0
      27: iload_1
      28: putfield      #4                  // Field number:I
      31: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: aload_0
      35: getfield      #4                  // Field number:I
      38: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V
      41: return

  int getDefaultNumber();
    Code:
       0: bipush        20
       2: ireturn
}

  • InheritTest.class
// InheritTest.class
Compiled from "InheritTest.java"
public class InheritTest {
  public InheritTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class Dog
       3: dup
       4: invokespecial #3                  // Method Dog."<init>":()V
       7: pop
       8: return
}

4. 字节码、源码对比分析

  • 由于main()方法中是直接调用Dog类的无参构造方法Constructor来创建类,因此我们主要来分析Dog类中字节码命令的执行过程
// Dog.class
Compiled from "InheritTest.java"
class Dog extends Animal {
  int number;

  final int defaultNumber;

  Dog();
    Code:
       0: aload_0
       1: bipush        20
       3: invokespecial #1                  // Method "<init>":(I)V
       6: return

  Dog(int);
    Code:
       0: aload_0
       1: invokespecial #2                  // Method Animal."<init>":()V
       4: aload_0
       5: aload_0
       6: invokevirtual #3                  // Method getDefaultNumber:()I
       9: putfield      #4                  // Field number:I
      12: aload_0
      13: bipush        20
      15: putfield      #5                  // Field defaultNumber:I
      18: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      21: ldc           #7                  // String This nonstatic block
      23: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      26: aload_0
      27: iload_1
      28: putfield      #4                  // Field number:I
      31: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: aload_0
      35: getfield      #4                  // Field number:I
      38: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V
      41: return

  int getDefaultNumber();
    Code:
       0: bipush        20
       2: ireturn
}

// Dog.java
class Dog extends Animal{
	int number = getDefaultNumber();
	final int defaultNumber = 20;
	{
		System.out.println("This nonstatic block");
	}
	Dog(){
		this(20);
	}
	
	Dog(int num){
		number = num;
		System.out.println(number);
	}
	
	int getDefaultNumber(){
		return defaultNumber;
	}
}

// Output:
// 11
// This nonstatic block
// 20

a. 无参构造方法Dog()

  • 首先分析Dog()方法:Dog()方法中的内容很简短,即直接调用当前类中的有参构造Dog(int),并传入整型参数20,不多赘述

b. 有参构造方法Dog(int)

  • 其次分析Dog(int)方法:Dog(int)方法首先隐式调用了父类Animal的无参构造,然后才执行本构造方法中的实际内容
  • 但是通过观察字节码指令可知,在通过编译构造函数Constructor生成类初始化方法<init>()时,并不只是将Constructor中的命令编译进<init>()方法中,同时还带有本类中实例成员的初始化代码以及非静态代码块编译成的字节码命令
  • 这就说明实例初始化<init>()方法在生成过程中,实际上还收集了所有实例成员的初始化代码非静态代码块{},收集顺序和源码顺序相同,并将其放置于调用父类构造方法之后,在执行Constructor方法剩余命令之前。
  • 由此我们得知<init>()方法的组成以及执行过程:调用Constructor中指定的父类<init>()方法 ==> 实例成员初始化语句和非静态代码 ==> Constructor方法

c. getDefaultNumber()方法

  • getDefaultNumber()方法中的内容也很简单,实际上就是返回一个实例常量成员,但值得注意的是,由于编译器的常量传播优化特性,在getDefaultNumber()方法中,直接使用常量20代替defaultNumber,而并不是返回常量defaultNumber的当前值
  • 通过对比源码和字节码命令我们也能看出,在调用getDefaultNumber()方法时,defaultNumber变量本身其实还并未被初始化,只是分配了内存,也就是说其当前值并非20,而是int类型的默认值0,但是由于Java编译器常量传播这一优化特性,因此getDefaultNumber()方法直接返回20

5. 总结

  • <init>()方法的组成以及执行过程:调用Constructor中指定的父类<init>()方法 ==> 实例成员初始化代码和非静态代码块 ==> Constructor方法剩余内容
  • 其中类中类变量赋值和静态代码块在类加载时已经执行,具体参照《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)》中类初始化相关内容
  • 类中的每个类构造Constructor方法(或者说类构造函数、类构造器)都会生成一个<init>()方法,根据实际情况调用其中的某一个或某几个
  • 注意:本次实验结果还说明,若在同一个类的Constructor方法中调用另一个Constructor方法(如无参构造调用有参构造),则不会在当前Constructor方法生成的<init>()方法中插入本类中的非静态代码,而是在最终调用父类初始化代码的<init>()方法中插入

参考资料

《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)》


End~

发布了22 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/TomAndersen/article/details/104349311