JAVA 构造函数 调用 多态的成员方法 的问题
2016-01-18 18:36
597 查看
在构造器中调用多态方法进行初始化,也许会产生不可预料的结果。
[java] view
plaincopy
import java.io.FileWriter;
import java.io.IOException;
class Shap {
void set() {
try {
System.out.println("Shap.set()");
FileWriter fw = new FileWriter("hello.txt", true);
String s = "Shap.set\n";
fw.write(s);
fw.flush();
fw.close();
} catch (IOException e) {
System.out.println("Shap.set e=" + e.getMessage());
}
}
Shap() {
System.out.println("Shap().in=");
set();
System.out.println("Shap().out=");
}
}
class Line extends Shap {
int counter = 9;
void set() {
try {
System.out.println("Line.set() counter="+counter);
FileWriter fw = new FileWriter("hello.txt", true);
String s = "Line.set counter=" + counter+"\n";
fw.write(s);
fw.flush();
fw.close();
} catch (IOException e) {
System.out.println("Line.set e=" + e.getMessage());
}
}
Line() {
System.out.println("Line().in=" + counter);
set();
System.out.println("Line().out=" + counter);
}
}
public class Mom {
public static final void main(String[] args) {
Line t = new Line();
}
}
预料中hello.txt中内容应该是这样:
[html] view
plaincopy
Shap.set
Line.set counter=9
实际hello.txt文件中内容如下:
[html] view
plaincopy
Line.set counter=0
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方法时,多态就不会出现,也就不会出现父类构造器调用子类方法的情况,这样可以保证父类始终调用自己的方法,即使这个方法中调用了父类中的实例变量也不会出现变量未初始化的情况(变量初始化总是在当前类构造器主体执行之前进行)。
可以参见本博的另外一篇文章
http://blog.csdn.net/bigtree_3721/article/details/50364675
[java] view
plaincopy
import java.io.FileWriter;
import java.io.IOException;
class Shap {
void set() {
try {
System.out.println("Shap.set()");
FileWriter fw = new FileWriter("hello.txt", true);
String s = "Shap.set\n";
fw.write(s);
fw.flush();
fw.close();
} catch (IOException e) {
System.out.println("Shap.set e=" + e.getMessage());
}
}
Shap() {
System.out.println("Shap().in=");
set();
System.out.println("Shap().out=");
}
}
class Line extends Shap {
int counter = 9;
void set() {
try {
System.out.println("Line.set() counter="+counter);
FileWriter fw = new FileWriter("hello.txt", true);
String s = "Line.set counter=" + counter+"\n";
fw.write(s);
fw.flush();
fw.close();
} catch (IOException e) {
System.out.println("Line.set e=" + e.getMessage());
}
}
Line() {
System.out.println("Line().in=" + counter);
set();
System.out.println("Line().out=" + counter);
}
}
public class Mom {
public static final void main(String[] args) {
Line t = new Line();
}
}
预料中hello.txt中内容应该是这样:
[html] view
plaincopy
Shap.set
Line.set counter=9
实际hello.txt文件中内容如下:
[html] view
plaincopy
Line.set counter=0
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方法时,多态就不会出现,也就不会出现父类构造器调用子类方法的情况,这样可以保证父类始终调用自己的方法,即使这个方法中调用了父类中的实例变量也不会出现变量未初始化的情况(变量初始化总是在当前类构造器主体执行之前进行)。
可以参见本博的另外一篇文章
http://blog.csdn.net/bigtree_3721/article/details/50364675
相关文章推荐
- SpringMVC重定向中文乱码解决方案
- Type Unknown error: java.lang.NullPointerException
- 6、ActiveMQ入门教程(六)Spring与ActiveMQ整合
- Java编程思想个人见解 第三章 操作符
- Java移位操作
- 5、ActiveMQ入门教程(五)--Spring和ActiveMQ整合
- JavaSE入门学习2:Java开发准备基础
- Spring MVC ajax提交方式
- Java实现多线程多节点下载
- 关于java中wait()和sleep()方法间的区别
- 转:JAVA.NET.SOCKETEXCEPTION: TOO MANY OPEN FILES解决方法
- 用spring提供的工具类读取Properties
- Java创建线程的两种方法比较
- Java 刷题
- Spring 2.0技术手册 -3
- Java工程师成神之路~
- java Integer和int之间==的比较问题 equal
- Stackoverflow上人气最旺的10个Java问题
- 关于在eclipse中ADT的安装
- java25:API