天天看点

关于java中创建对象时属性的初始化过程

java是一种面向对象的编程语言,那么了解创建对象时程序会怎么执行就变得尤为重要,下面我们就一起看看在我们使用new关键字创建对象时是怎么对属性初始化的:

下面是一个Person类,其中有成员变量age和静态变量name

public class Person {

    private int age;            //年龄

    public static String name = "abc";           //姓名

    public Person(int age) {
        this.age = age;
    }
}
           

编译后接着用javap命令对其反编译:

{
  public static java.lang.String name;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC

  public Person(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iload_1							  //将int类型的变量(构造方法中传的age)推至栈顶
         6: putfield      #2                  //对age属性进行赋值
         9: return
      LineNumberTable:
        line 9: 0
        line 10: 4
        line 11: 9

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #3                  // 将"abc"从常量池推至栈顶
         2: putstatic     #4                  // 对name属性赋值
         5: return
      LineNumberTable:
        line 7: 0
}
           

在这里我们可以发现,对于成员属性来说,是在构造方法中初始化的,对于static属性编译器会在static代码块中对其初始化(按static语句的声明顺序)。那么如果我们直接在成员属性上显示赋值而不在构造方法中初始化呢:

为了方便观察,我在构造方法里加入了一个输出语句。

public class Person {

    private int age = 13;            //年龄

    public static String name = "abc";           //姓名

    public Person() {
        System.out.println("构造方法");
    }
}
           

javap反编译后:

public Person();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: bipush        13				  //将单字节的常量值(-128~127)推送至栈顶
         7: putfield      #2                  // Field age:I(对属性age赋值)
		------------------ 下面是关于输出语句的代码 ------------------
        10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: ldc           #4                  // String 构造方法
        15: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        18: return
      LineNumberTable:
        line 9: 0
        line 5: 4
        line 10: 10
        line 11: 18
           

可以看到,对于直接对成员属性显示赋值的情况,编译器会将它放到构造方法中代码的前面执行,比如上面的代码就把对age属性的赋值放在了输出语句的前面。

为了更直观的观察创建对象时,jvm对属性的赋值过程,我们在idea里debug一下:

关于java中创建对象时属性的初始化过程
关于java中创建对象时属性的初始化过程
关于java中创建对象时属性的初始化过程

到这里,整个对象的初始化就完成了。可以发现,当字节码文件被加载到jvm中后,类中static属性其实并没有马上被赋值,而是先有了其类型对应的默认值,成员属性在new关键字开辟内存空间后,也是先有了默认值,直到构造方法对其赋值。

那么对于static final修饰的变量也是如此吗?

关于java中创建对象时属性的初始化过程

总结:

在我们使用new关键字创建一个对象的时候。

1.jvm会先去加载这个对象的类的字节码文件,并把其中static的属性放到方法区保存,此时这些属性还是其类型对应的默认值,(static final的变量除外,jvm会直接对其赋值)。

2.紧接着jvm会对这些方法区中的static变量进行赋值。

3.在方法区中static属性赋值完成后,new关键字就会在堆中去申请一块空间用来存储对象,此时对象中的成员属性就正式在内存中存在了,但会被赋予其类型的默认值。

4.在new关键字在堆中开辟内存空间后,jvm就会去调用对象的构造方法,在构造方法中对成员属性开始赋值,当构造方法执行完毕,成员属性也随之完成了赋值。到这里,整个对象的创建过程就算结束了。