【Thinking in Java】编写构造器时应注意:尽量避免调用其他非private方法
最近重温了《Thinking in Java》,发现了一个让我为之兴奋的知识漏洞,必须得分享一下。
上一篇的《Java类初始化的过程》的随笔中,那个初始化顺序并不完整。初始化的实际过程是:
- 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的0;
- 如上一篇的《Java类初始化的过程》那样:父类的static成员变量和方法-->该类的static变量和方法-->开始实例化-->父类的普通成员变量和方法-->父类的构造方法-->该类的普通成员变量和方法-->该类的构造方法-->实例化结束
然而,我们知道,当子类Sub继承了父类Sup、并重写了父类的方法draw()后,我们即使向上转型为父类(即Sup sup=new Sub()),当我们调用sup.draw()方法的时候,它实际上调用的是Sub的draw方法。这里就有个坑了!
如果在父类的构造方法里调用draw()方法,从逻辑上,我们以为是调用父类的draw()方法,而实际上,即使在父类的构造器以内,Java编译器让它调用的还是子类的draw()方法。
我们用一个例子来展示一下:
public class Glyph {
Glyph(){
System.out.println("Glyph before draw()");
draw(); //逻辑上本应该调用本类的draw(),然而结果不是
System.out.println("Glyph after draw()");
}
void draw(){
System.out.println("Glyph.draw()");
}
}
public class RoundGlyph extends Glyph{
private int radius=1;
public RoundGlyph(int r) {
radius=r;
System.out.println("RoundGlyph.RoundGlyph(),radius="+radius);
}
@Override
void draw() {
System.out.println("RoundGlyph.draw(),radius="+radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
打印结果是:
Glyph before draw()
RoundGlyph.draw(),radius=0
Glyph after draw()
RoundGlyph.RoundGlyph(),radius=5
看打印的第二行,逻辑上我们打印的本应该是Glyph.draw(),然而它被子类覆盖了。而且,就算打印的是RoundGlyph.draw(),radius=0,那radius应该等于1才对啊!不是的,按照类的初始化顺序,先初始化的是父类Glyph的构造器,然后才轮到初始化RoundGlyph的成员变量radius。之所以radius=0,是因为Java类的加载机制的准备阶段,即在用户初始化变量之前,就已经为变量初始化为0了,关于Java虚拟机的类加载机制,可以看下《深入理解Java虚拟机》一书的第7章。
这种bug很难查找,但是又会破坏程序本身,让我们忘bug兴叹。
所以我们在对构造器进行初始化的时候,要尽量简单,尽量避免在构造方法内调用其他public的非构造方法(private方法可以调用,因为它不可被继承)。
【Thinking in Java】编写构造器时应注意:尽量避免调用其他非private方法的更多相关文章
- 原!! java直接打印一个对象时,并不是直接调用该类的toString方法 ,而是会先判断是否为null,非null才会调用toString方法
网上看了好多java直接打印一个对象时,直接调用该类的toString方法 . 但是: Object obj=null; System.out.println(obj);//没有报错 System.o ...
- [Xcode 实际操作]八、网络与多线程-(18)PerformSelector消息处理方法:由运行时系统,负责去调用对象的指定方法
目录:[Swift]Xcode实际操作 本文将演示PerformSelector消息处理方法. 在项目文件夹上点击鼠标右键弹出文件菜单. [New File]->[Swift File]-> ...
- java中main函数怎么调用外部非static方法
使用外部方法时(不管是static还是非static),都要先new一个对象,才能使用该对象的方法. 举例如下: 测试函数(这是错误的): public class Test { public sta ...
- java写文件时,输出不完整的原因以及解决方法
在java的IO体系中,写文件通常会用到下面语句 BufferedWriter bo=new BufferedWriter(new FileWriter("sql语句.txt")) ...
- java写文件时,输出不完整的原因以及解决方法close()或flush()
在java的IO体系中,写文件通常会用到下面语句 BufferedWriter bw=new BufferedWriter(new FileWriter("sql语句.txt")) ...
- 【java】子类可以通过调用父类的public方法调用父类的private方法,为什么?
代码1: 打印结果: 代码2: 运行结果: 问题: 代码1中super是父类自己调用自己的add()方法,并在add()方法中调用了私有的del()方法,那为什么打印出来的this是子类? 代码2中t ...
- 《深入理解Java虚拟机》(二)Java虚拟机运行时数据区
Java虚拟机运行时数据区 详解 2.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第二章 ,为了整理思路,简单记录一下,方便后期查阅. 2.2 运行时数据区域 Java虚拟机 ...
- Java 构造器 遇到多个构造器时要考虑用构建器
静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数. 当一个类中有若干个必选属性和多个可选属性时,采用重叠构造器模式.JavaBeans模式或者Builder模式,但各有优劣. 当 ...
- Java编程 “提高性能” 应尽力做到
除了新增机器内存外,还应该好好review一下我们的代码,有很多代码编写过于随意化,这些不好的习惯或对程序语言的不了解是应该好好打压打压了. 下面是参考网络资源总结的一些在Java编程中尽可能要做到的 ...
随机推荐
- Android菜鸟成长记8 -- 布局实践(微信界面的编写)
前面我们简单的介绍了一下android的五大布局,那么现在我们来实践一下,写一个简单的微信界面 首先,我们新建一个weixin.xml的linnerlayout布局 我们日常使用的微信,从简单的方面来 ...
- SQL Server更新表(用一张表的数据更新另一张表的数据)
a) 写法轻松,更新效率高: update table1 set field1=table2.field1,field2=table2.field2 from table2 where table1. ...
- QT征程之初识qt
下载 https://www.qt.io/cn/download-open-source/ 下载QT离线安装包 Qt 5.5.1 for Linux 32-bit (546 MB) (info ...
- jQuery 取消事件冒泡 阻止后续内容执行 闭包函数 (学习笔记)
1.取消事件冒泡 <title>取消事件冒泡</title> <style> div { border:solid 1px black; } </style& ...
- PHP入门篇
一.PHP-什么是变量 变量是用于存储值的,我们命令服务器去干活的时候,往往需要产生一些数据,需要临时性存放起来,方便取用.我们也可以理解为,变量就像一个购物袋,我们可以用来装苹果.榴莲(当然也可以 ...
- ubuntu远程桌面连接windows系统(转)
现在用ubuntu系统,公司买了个windows的服务器,需要给配置一套环境,来回跑很麻烦,就想windows下可以的远程桌面,linux应该也有. 现在自己的ubuntu13.10,无法进入桌面的& ...
- SQL中CONVERT日期不同格式的转换用法
SQL中CONVERT日期不同格式的转换用法 格式: CONVERT(data_type,expression[,style]) 说明:此样式一般在时间类型(datetime,smalldatetim ...
- JSP中的 HttpSession、pageContext对象
pageContext 隐含对象对应javax.servlet.jsp.PageContext,都自动的被加入至pageContext中, 您可以由它来取得与JSP相关的对应之Servlet对象,像是 ...
- NOT IN查询效率低,用它的等效写法提高效率。
最近在处理大数据量导入的时候,使用OPENROWSET将Excel导入到临时表中之后,需要对数据进行唯一性验证.这时候发现使用NOT IN严重影响效率,一条sql可能执行10分钟甚至更久.尝试改变写法 ...
- typedef 与指针、多维数组
1.在typedef中使用指针往往会带来意外的结果.如下: typedef string *pstring; const pstring cstr; 绝大数人刚开始都会认为cstr是一种指针,它指向c ...