创建一个对象大概有以下几种方式:
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,是为什么呢,内部的机制是怎样的?先来了解点其它内容。
在编译代码的时候,会为每个构造方法生成一个对应的方法,方法名叫<init>。但并不是直接将构造方法体作为<init>方法的内容,它有这样的规则:
如果构造方法中的第一条语句是通过this调用本类的其它构造方法,如类S的第一个构造方法,其完整的构造方法体就是对应的<init>方法的方法体。编译器不会为其添加一个super调用了。
如果构造方法中的第一条语句不是通过this调用本类的其它构造方法,会按以下内容与顺序组成<init>方法体:
1、超类<init>方法的调用。如果是显式的调用了超类构造方法,将会使用对应的超类<init>方法,如果没有写,编译器会生成一个超类无参<init>方法的调用;
2、实例变量初始化代码,按实例变量在类中出现的顺序;
3、构造方法中的其它方法体(如果第一句是super(…)调用,则不包含该句)。
如果构造方法中包含super(…)或this(…)调用,那么它们只能作为该构造方法的第一条语句,也就是说连try…catch都不可以有。因为必须为第一条语句,所以super(…)和this(…)调用是不会出现在一起的。
用javap -c S反编译获得各<init>方法的字节码如下:
public S() throws java.lang.Exception; Code: 0 : aload_0 1 : ldc # 2 // String S() 3 : invokespecial # 3 // Method "<init>":(Ljava/lang/String;)V 6 : return public S(java.lang.String) throws java.lang.Exception; Code: 0 : aload_0 1 : invokespecial # 4 // Method P."<init>":()V 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."<init>":()V 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()中,字节码表现出的只是调用了本类的<init>(String)方法;从第二个构造方法S(String)其对应的<init>字节码中可以看出,虽然没有用super(…)显式的调用超类的构造方法,编译器还是生成了偏移量为0,1的指令用于调用超类的<init>方法,偏移量为4,5,6的指令是给变量value1赋值为4,后面即为构造方法体——唯一的一条打印语句;再看S(int)对应的<init>方法,构造方法的第一条语句为super(…)调用,对应着<init>中的偏移量为0,1的指令;偏移量为4,5,6的指令是为实例变量value1赋值为4;最后是打印字符串abc。
再看看P反编译后构造方法体:
public P() throws java.lang.Exception; Code: 0 : aload_0 1 : invokespecial # 1 // Method java/lang/Object."<init>":()V 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的<init>方法,也就是类P的超类;指令4,5,6为v1赋值为5,指令9,10,11,14是为实例变量v2获取值并赋值;剩余的是打印字符串P。
从上面几个构造方法与对应的<init>方法分析中可以看出,它们都是符合前文所述的规则的。那么对于本文开头的例子v2的值为何为0就好分析了:
1、new S()的时候调用的是S类的<init>()方法,它去调用了本类的<init>(String)方法,在这个<init>中,先去调用超类,也就是P的<init>()方法,P的<init>()方法又去调用P的超类Object的<init>()方法,然后去给v1,v2赋值,v2赋值调用的是getV1(),而这个方法是一个多态方法,它会去调用new对象的那个类的getV1方法,也就是S中的getV1,S中的getV1返回的是value1的值,而此时value1赋值操作还没有做呢,所以返回的是它的默认值0.
如此,整个创建对象期间的顺序就一清二楚了。