创建一个对象大概有以下几种方式:
1、通过new关键字,如new Object();
2、通过某些反射类的newInstance方法,如Class#newInstance、Constructor#newInstance;
3、如果对象是Cloneable的,通过clone方法;
4、通过ObjectInputStream#readObject反序列化;
以上是通过java程序可以创建出对象的方式,jvm中还有一些隐式创建对象的地方,譬如:
1、启动一个类,main方法的参数String数组是隐式创建的,如果指定了一个或多个String对象,还要创建这些String对象;
2、读入一个class二进制数据的时候,创建一个与之对应的java.lang.Class类的对象;
3、在使用“+”进行字符串变量连接时,可能会创建StringBuffer/StringBuilder对象;
如此等等。
那么,在程序中通过new或newInstance创建对象(后面说创建对象均指这两种方式)的时候,构造方法、实例变量、父类构造方法、父类实例变量等的执行顺序是怎样的?
在创建对象的时候,首先会分配内存,此时所有实例变量均为默认值,然后做初始化实例变量、构造方法调用等操作。对于类变量,在创建对象之前,加载类的时候已经做掉了,这里为避免干扰,忽略掉。
先来一个例子:
public
class
Init {
public
static
void
main(String[] args)
throws
Exception {
S s =
new
S();
System.out.println(s.getV2());
}
}
class
P {
private
int
v1 =
5
;
private
int
v2 = getV1();
public
P()
throws
Exception {
System.out.println(
"P"
);
}
public
int
getV1() {
return
v1;
}
public
int
getV2() {
return
v2;
}
}
class
S
extends
P {
private
int
value1 =
4
;
public
int
getV1() {
return
value1;
}
public
S()
throws
Exception {
this
(
"S()"
);
}
public
S(String msg)
throws
Exception {
System.out.println(msg);
}
public
S(
int
v)
throws
Exception {
super
();
System.out.println(
"abc"
);
}
}
|
执行结果如下:
P S() 0
调用s.getV2()的值为0,是为什么呢,内部的机制是怎样的?先来了解点其它内容。
在编译代码的时候,会为每个构造方法生成一个对应的方法,方法名叫
如果构造方法中的第一条语句是通过this调用本类的其它构造方法,如类S的第一个构造方法,其完整的构造方法体就是对应的
如果构造方法中的第一条语句不是通过this调用本类的其它构造方法,会按以下内容与顺序组成
1、超类
2、实例变量初始化代码,按实例变量在类中出现的顺序;
3、构造方法中的其它方法体(如果第一句是super(…)调用,则不包含该句)。
如果构造方法中包含super(…)或this(…)调用,那么它们只能作为该构造方法的第一条语句,也就是说连try…catch都不可以有。因为必须为第一条语句,所以super(…)和this(…)调用是不会出现在一起的。
用javap -c S反编译获得各
public
S()
throws
java.lang.Exception;
Code:
0
: aload_0
1
: ldc #
2
// String S()
3
: invokespecial #
3
// Method "
6
:
return
public
S(java.lang.String)
throws
java.lang.Exception;
Code:
0
: aload_0
1
: invokespecial #
4
// Method P."
4
: aload_0
5
: iconst_4
6
: putfield #
1
// Field value1:I
9
: getstatic #
5
// Field java/lang/System.out:Ljava/io/PrintStream;
12
: aload_1
13
: invokevirtual #
6
// Method java/io/PrintStream.println:(Ljava/lang/String;)V
16
:
return
public
S(
int
)
throws
java.lang.Exception;
Code:
0
: aload_0
1
: invokespecial #
4
// Method P."
4
: aload_0
5
: iconst_4
6
: putfield #
1
// Field value1:I
9
: getstatic #
5
// Field java/lang/System.out:Ljava/io/PrintStream;
12
: ldc #
7
// String abc
14
: invokevirtual #
6
// Method java/io/PrintStream.println:(Ljava/lang/String;)V
17
:
return
|
可以看出,在类S的第一个构造方法s()中,字节码表现出的只是调用了本类的
再看看P反编译后构造方法体:
public
P()
throws
java.lang.Exception;
Code:
0
: aload_0
1
: invokespecial #
1
// Method java/lang/Object."
4
: aload_0
5
: iconst_5
6
: putfield #
2
// Field v1:I
9
: aload_0
10
: aload_0
11
: invokevirtual #
3
// Method getV1:()I
14
: putfield #
4
// Field v2:I
17
: getstatic #
5
// Field java/lang/System.out:Ljava/io/PrintStream;
20
: ldc #
6
// String P
22
: invokevirtual #
7
// Method java/io/PrintStream.println:(Ljava/lang/String;)V
25
:
return
|
偏移量为0,1的指令调用Object的
从上面几个构造方法与对应的
1、new S()的时候调用的是S类的
如此,整个创建对象期间的顺序就一清二楚了。