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.0id = 6944, name = Employee #6944, salary = 60000.0id = 6945, name = , salary = 0.0
根据第一条语句得出静态初始化块生成的nextId为6943,然后在初始化块中id被赋值为6943,nextId自增后为6944。再执行第一个构造函数;
那么对于第二个对象来说,就应该直接执行初始化块,此时id为6944,nextId自增为6945。再执行第二个构造函数,此时this('Employee #' + nextId, salary);语句中的nextId应该为6945,为什么输出结果为6944呢?
问题解答
回答1:这个类初始化的顺序确实是个神奇的问题,只可根据结果去理解。我打了个断点去测试,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,则按你的预期,代码块先于构造方法。
public Employee(double salary) { // 1 this('Employee #' + nextId, salary); // 2// this.name = 'Employee #' + nextId; // this.salary = salary;}回答2:
正常来说,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.0id = 5474, name = Employee #5475, salary = 60000.0id = 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
相关文章:
1. mysql - 为什么innodb下更新A行时B行也被锁住?2. python - 如何给模块传参数,参数是模块的函数名?3. mysql - spring data jpa 方法sql复杂查询?4. 微信小程序如何将获取的时间戳提交到数据库?5. 请问python中为什么我用for循环对嵌套列表进行赋值时,都是以i的最终值来计算的?6. python - Scrapy中xpath用到中文报错7. node.js - 微信的自动回复问题8. javascript - 我写的href跳转地址不是百度,为什么在有的机型上跳转到百度了,有的机型跳转正确9. python 多进程 或者 多线程下如何高效的同步数据?10. node.js - nodejs中mysql子查询返回多行结果怎么处理?