Java对象的创建过程与初始化过程
此文聚焦“对象”而不是“类”
1、Java对象创建过程
图片来源:https://www.cnblogs.com/chenyangyao/p/5296807.html
2、Java对象的初始化(类的实例化)
类的初始化不在本文范畴
此部分对应上图的调用对象的<init>方法
。<init>()
又叫做实例构造器,与之类似的有<clinit>()
称为类构造器(用来初始化类,具体见《JVM类的生命周期与类的加载过程》)。
注意:在此之前,在虚拟机为对象分配内存后,已经进行过一次初始化了
然后才真正按照程序员的意志进行初始化:
- 实例变量初始化
- 实例代码块初始化
- 构造函数初始化
1)实例变量初始化与实例代码块初始化
二者地位相同,编译器会按照二者的相对顺序将其插入到类的构造函数中,位置在超类构造函数之后(如果显示调用了),本构造函数体中的剩余代码之前。
举例:
public class InstanceVariableInitializer {
private int i = 1;
private int j = i + 1;
public InstanceVariableInitializer(int var){
System.out.println(i);
System.out.println(j);
this.i = var;
System.out.println(i);
System.out.println(j);
}
{ // 实例代码块
j += 3;
}
public static void main(String[] args) {
new InstanceVariableInitializer(8);
}
}
/* Output:
1
5
8
5
*///:~
上例的构造函数等价于:
public InstanceVariableInitializer(int var){
// 这里隐含了super.InstanceVariableInitializer()
// 以下是实例变量与实例代码块等价整合到了构造函数中
this.i = 1;
this.j = i + 1;
this.j = this.j + 3;
// 本构造函数的函数体
System.out.println(i);
System.out.println(j);
this.i = var;
System.out.println(i);
System.out.println(j);
}
通过查看字节码也可以验证这种整合:,如2)中的第一个例子中的字节码就有体现。
注意:不允许顺序靠前的实例代码块初始化在其后面定义的实例变量,如:
编译不通过
public class InstanceInitializer {
{
j = i;
}
private int i = 1;
private int j;
}
public class InstanceInitializer {
private int j = i;
private int i = 1;
}
取巧可以绕过上述问题:
public class InstanceInitializer {
private int j = getI();
private int i = 1;
public InstanceInitializer() {
i = 2;
}
private int getI() {
return i;
}
public static void main(String[] args) {
InstanceInitializer ii = new InstanceInitializer();
System.out.println(ii.j);
}
}
这种方式虽然编译器不报错,但j的结果为0,因为在初始化j时,还没有执行构造函数中的i = 2
,因此 i 为内存分配完统一初始化的0值(还没到<init>
)。
2)构造函数初始化
构造函数在编译生成的字节码中,会被命名成<init>()
方法,参数列表与Java语言书写的构造函数的参数列表相同。如:
private int id;
private String name;
private String address;
// 构造函数
public Student(int id, String name) {
this.id = id;
this.name = name;
}
{
address = "kk";
}
// 对应字节码
// access flags 0x1
public <init>(ILjava/lang/String;)V
L0
LINENUMBER 15 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V // 调用父类构造函数
L1
LINENUMBER 20 L1
ALOAD 0
LDC "kk"
PUTFIELD Student.address : Ljava/lang/String; // 将实例代码块织入实例构造器<init>中
L2
LINENUMBER 16 L2
ALOAD 0
ILOAD 1
PUTFIELD Student.id : I // 构造函数中的语句对应
L3
LINENUMBER 17 L3
ALOAD 0
ALOAD 2
PUTFIELD Student.name : Ljava/lang/String; // 构造函数中的语句对应
L4
LINENUMBER 18 L4
RETURN
L5
LOCALVARIABLE this LStudent; L0 L5 0
LOCALVARIABLE id I L0 L5 1
LOCALVARIABLE name Ljava/lang/String; L0 L5 2
MAXSTACK = 2
MAXLOCALS = 3
特别地,如果我们在一个构造函数中调用另外一个构造函数,如下所示
public class ConstructorExample {
private int i;
ConstructorExample() {
this(1); // 调用下面的构造函数
....
}
ConstructorExample(int i) {
....
this.i = i;
....
}
}
对于这种情况,Java只允许在ConstructorExample(int i)
内调用超类的构造函数,也就是说,下面两种情形的代码编译是无法通过的:
public class ConstructorExample {
private int i;
// 1.
ConstructorExample() {
super();
this(1); // Error:Constructor call must be the first statement in a constructor
....
}
// 2.
ConstructorExample() {
this(1);
super(); //Error: Constructor call must be the first statement in a constructor
....
}
ConstructorExample(int i) {
....
this.i = i;
....
}
}
总之,java对象的初始化(类的实例化)是一个递归的过程,如下图所示:
参考:https://blog.csdn.net/justloveyou_/article/details/72466416