对象实例化的顺序

对象实例化的顺序

分类:java基础日期:2012-11-08作者:ticmy

 

创建一个对象大概有以下几种方式:
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.

如此,整个创建对象期间的顺序就一清二楚了。

原文地址:https://www.cnblogs.com/01picker/p/4306225.html