在构造器中调用多态方法进行初始化,也许会产生不可预料的结果。




[java]  
    view plain 
    copy 
    
 
    
 
  
1. import java.io.FileWriter;  
2. import java.io.IOException;  
3.   
4.  
5. class Shap {  
6. void set() {  
7. try {  
8. "Shap.set()");  
9. new FileWriter("hello.txt", true);  
10. "Shap.set\n";  
11.             fw.write(s);  
12.             fw.flush();  
13.             fw.close();  
14. catch (IOException e) {  
15. "Shap.set e=" + e.getMessage());  
16.         }  
17.     }  
18.   
19.     Shap() {  
20. "Shap().in=");  
21.         set();  
22. "Shap().out=");  
23.     }  
24. }  
25.   
26. class Line extends Shap {  
27. int counter = 9;  
28.   
29. void set() {  
30. try {  
31. "Line.set() counter="+counter);  
32. new FileWriter("hello.txt", true);  
33. "Line.set counter=" + counter+"\n";  
34.             fw.write(s);  
35.             fw.flush();  
36.             fw.close();  
37. catch (IOException e) {  
38. "Line.set e=" + e.getMessage());  
39.         }  
40.     }  
41.   
42.     Line() {  
43. "Line().in=" + counter);  
44.         set();  
45. "Line().out=" + counter);  
46.     }  
47. }  
48.   
49. public class Mom {  
50. public static final void main(String[] args) {  
51. new Line();  
52.     }  
53. }



预料中hello.txt中内容应该是这样:



[html]  
    view plain 
    copy 
    
 
    
 
  
1. Shap.set  
2. Line.set counter=9



实际hello.txt文件中内容如下:



[html]  
    view plain 
    copy 
    
 
    
 
  
1. Line.set counter=0  
2. Line.set counter=9




原因:

《Thinking in java》中指出,这是由于构造器初始化顺序的问题:

初始化的实际过程是:

1、在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制零。
2、调用基类构造器。这个步骤会不断的反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,等等。直到最低层的导出类。此时,调用被重载的<pre name="code" class="html">set()方法(是的,是在调用Line构造器之前调用的),由于步骤(1)的缘故,我们此时会发现counter的值为0。
3、按照声明的顺序调用成员的初始化代码。在类的内部,初始化的顺序是先“静态”,(如果它们尚未因前面的对象创建过程而被初始化),后“非静态”。而非静态变量定义的顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。
4、调用导出类的构造器主体。


结论:
如果你要在构造器中调用一个方法时,将该方法声明为private。
对于这个规则是需要一些说明的,假使你的父类构造器中要调用一个非静态方法,而这个方法不是private的又被子类所重载,这样在实际创建子类的过程中递归调用到了父类的构造器时,父类构造器对这个方法的调用就会由于多态而实际上调用了子类的方法,当这个子类方法需要用到子类中实例变量的时候,就会由于变量没有初始化而出现异常(至于为什么子类中的实例变量没有初始化可以参考上边的实例初始化过程),这是Java不想看到的情况。而当父类构造器中调用的方法是一个private方法时,多态就不会出现,也就不会出现父类构造器调用子类方法的情况,这样可以保证父类始终调用自己的方法,即使这个方法中调用了父类中的实例变量也不会出现变量未初始化的情况(变量初始化总是在当前类构造器主体执行之前进行)。

可以参见本博的另外一篇文章





[java]  
    view plain 
    copy 
    
 
    
 
  
1. import java.io.FileWriter;  
2. import java.io.IOException;  
3.   
4.  
5. class Shap {  
6. void set() {  
7. try {  
8. "Shap.set()");  
9. new FileWriter("hello.txt", true);  
10. "Shap.set\n";  
11.             fw.write(s);  
12.             fw.flush();  
13.             fw.close();  
14. catch (IOException e) {  
15. "Shap.set e=" + e.getMessage());  
16.         }  
17.     }  
18.   
19.     Shap() {  
20. "Shap().in=");  
21.         set();  
22. "Shap().out=");  
23.     }  
24. }  
25.   
26. class Line extends Shap {  
27. int counter = 9;  
28.   
29. void set() {  
30. try {  
31. "Line.set() counter="+counter);  
32. new FileWriter("hello.txt", true);  
33. "Line.set counter=" + counter+"\n";  
34.             fw.write(s);  
35.             fw.flush();  
36.             fw.close();  
37. catch (IOException e) {  
38. "Line.set e=" + e.getMessage());  
39.         }  
40.     }  
41.   
42.     Line() {  
43. "Line().in=" + counter);  
44.         set();  
45. "Line().out=" + counter);  
46.     }  
47. }  
48.   
49. public class Mom {  
50. public static final void main(String[] args) {  
51. new Line();  
52.     }  
53. }

 



预料中hello.txt中内容应该是这样:



[html]  
    view plain 
    copy 
    
 
    
 
  
1. Shap.set  
2. Line.set counter=9




实际hello.txt文件中内容如下:



[html]  
    view plain 
    copy 
    
 
    
 
  
1. Line.set counter=0  
2. Line.set counter=9




原因:

《Thinking in java》中指出,这是由于构造器初始化顺序的问题:

初始化的实际过程是:

1、在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制零。
2、调用基类构造器。这个步骤会不断的反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,等等。直到最低层的导出类。此时,调用被重载的<pre name="code" class="html">set()方法(是的,是在调用Line构造器之前调用的),由于步骤(1)的缘故,我们此时会发现counter的值为0。
3、按照声明的顺序调用成员的初始化代码。在类的内部,初始化的顺序是先“静态”,(如果它们尚未因前面的对象创建过程而被初始化),后“非静态”。而非静态变量定义的顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化。
4、调用导出类的构造器主体。


结论:
如果你要在构造器中调用一个方法时,将该方法声明为private。
对于这个规则是需要一些说明的,假使你的父类构造器中要调用一个非静态方法,而这个方法不是private的又被子类所重载,这样在实际创建子类的过程中递归调用到了父类的构造器时,父类构造器对这个方法的调用就会由于多态而实际上调用了子类的方法,当这个子类方法需要用到子类中实例变量的时候,就会由于变量没有初始化而出现异常(至于为什么子类中的实例变量没有初始化可以参考上边的实例初始化过程),这是Java不想看到的情况。而当父类构造器中调用的方法是一个private方法时,多态就不会出现,也就不会出现父类构造器调用子类方法的情况,这样可以保证父类始终调用自己的方法,即使这个方法中调用了父类中的实例变量也不会出现变量未初始化的情况(变量初始化总是在当前类构造器主体执行之前进行)。