搜索

首页  >  问答  >  正文

Java类的实例化顺序

在验证《Core Java》第9版4-5代码时,发现程序输出结果和自己理解的不太一样。

import java.util.Random;

class Employee {
    private static int nextId;

    private int id;
    private String name = "";
    private double salary;

    static {
        Random generator = new Random();
        nextId = generator.nextInt(10000);
    }

    {
        id = nextId;
        nextId++;
    }

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public Employee(double salary) {
        this("Employee #" + nextId, salary);
    }

    public Employee() {

    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public int getId() {
        return id;
    }

}

public class ConstructorTest {

    public static void main(String[] args) {
        Employee[] staff = new Employee[3];

        staff[0] = new Employee("Harry", 40000);

        staff[1] = new Employee(60000);

        staff[2] = new Employee();

        for (Employee e : staff) {
            System.out.println("id = " + e.getId() + ", name = " + e.getName()
                    + ", salary = " + e.getSalary());
        }
    }

}

以下是输出结果:

id = 6943, name = Harry, salary = 40000.0
id = 6944, name = Employee #6944, salary = 60000.0
id = 6945, name = , salary = 0.0

根据第一条语句得出静态初始化块生成的nextId为6943,然后在初始化块中id被赋值为6943,nextId自增后为6944。再执行第一个构造函数;

那么对于第二个对象来说,就应该直接执行初始化块,此时id为6944,nextId自增为6945。
再执行第二个构造函数,此时this("Employee #" + nextId, salary);语句中的nextId应该为6945,为什么输出结果为6944呢?

巴扎黑巴扎黑2804 天前718

全部回复(2)我来回复

  • 迷茫

    迷茫2017-04-18 10:49:39

    这个类初始化的顺序确实是个神奇的问题,只可根据结果去理解。
    我打了个断点去测试,staff[0] = new Employee("Harry", 40000);staff[2] = new Employee();都是代码块先于构造方法执行,但staff[1] = new Employee(60000);却先执行走到this("Employee #" + nextId, salary);,然后代码块,然后public Employee(String name, double salary)构造函数。staff[0] = new Employee("Harry", 40000);staff[2] = new Employee();都是代码块先于构造方法执行,但staff[1] = new Employee(60000);却先执行走到this("Employee #" + nextId, salary);,然后代码块,然后public Employee(String name, double salary)构造函数。
    如果你使用2如果你使用2,则按你的预期,代码块先于构造方法。

    public Employee(double salary) {
        // 1
        this("Employee #" + nextId, salary); 
        // 2
    //    this.name = "Employee #" + nextId; 
    //    this.salary = salary;
    }

    回复
    0
  • PHPz

    PHPz2017-04-18 10:49:39

    正常来说,java 编译器会把实例初始化块复制构造方法中,具体位置在调用父类的构造方法以后,构造方法里面的语句之前,但是存在例外情况。Java 官方的 Tutorials 里说初始化块会被复制到每个构造方法里面其实是不严谨的。

    具体到这个例子,需要考虑一个问题,如果编译器把初始化块复制到每个构造方法里面,那么对于在构造方法里面调用了其他构造方法的情况,这个初始化块就会执行两次,就像例子里面的

    public Employee(double salary) {
            this("Employee #" + nextId, salary);  // 调用了另一个构造方法
    }

    如果编译器把初始化块里的代码复制到了public Employee(double salary)public Employee(String name, double salary)里面,这个初始化块就会执行两次,为了避免这种情况,编译器作了一个简单的处理,编译器发现public Employee(double salary)调用了本类的另一个构造方法,就没有把初始化块的代码拷贝到这个构造方法里面。
    也就是说在初始化第二个对象的时候,这个初始化块是推迟到调用this("Employee #" + nextId, salary);后,在执行Employee(String name, double salary)的时候才执行的,由于推迟了初始化块的执行,在决定传递的参数 nextId 的时候,仍然是未自增的值。
    如果把这个构造方法修改为

    public Employee(double salary) {
        // this("Employee #" + nextId, salary);
        this.name = "Employee #" + nextId;
        this.salary = salary;
    }

    输出结果就会变为

    id = 5473, name = Harry, salary = 40000.0
    id = 5474, name = Employee #5475, salary = 60000.0
    id = 5475, name = , salary = 0.0

    而修改之前的情况,反编译下 class 文件就能看出来编译器最后的输出结果,这里只贴三个构造方法,可以很明显的看出来,第二个构造方法并没有被复制初始化块的内容,直接调用了另一个构造方法。

      public Employee(java.lang.String, double);
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: aload_0
           5: ldc           #2                  // String
           7: putfield      #3                  // Field name:Ljava/lang/String;
          10: aload_0
          11: getstatic     #4                  // Field nextId:I
          14: putfield      #5                  // Field id:I
          17: getstatic     #4                  // Field nextId:I
          20: iconst_1
          21: iadd
          22: putstatic     #4                  // Field nextId:I
          25: aload_0
          26: aload_1
          27: putfield      #3                  // Field name:Ljava/lang/String;
          30: aload_0
          31: dload_2
          32: putfield      #6                  // Field salary:D
          35: return
    
      public Employee(double);
        Code:
           0: aload_0
           1: new           #7                  // class java/lang/StringBuilder
           4: dup
           5: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
           8: ldc           #9                  // String Employee #
          10: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          13: getstatic     #4                  // Field nextId:I
          16: invokevirtual #11                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
          19: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
          22: dload_1
          23: invokespecial #13                 // Method "<init>":(Ljava/lang/String;D)V
          26: return
    
      public Employee();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: aload_0
           5: ldc           #2                  // String
           7: putfield      #3                  // Field name:Ljava/lang/String;
          10: aload_0
          11: getstatic     #4                  // Field nextId:I
          14: putfield      #5                  // Field id:I
          17: getstatic     #4                  // Field nextId:I
          20: iconst_1
          21: iadd
          22: putstatic     #4                  // Field nextId:I
          25: return

    回复
    0
  • 取消回复