在验证《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呢?
迷茫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);< /code>,然後程式碼區塊,然後
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;
}
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