【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编程中尽可能要做到的 ...
随机推荐
- python学习笔记4-redis multi watch实现锁库存
python 关于redis的基本操作网上已经很多了,这里主要介绍点个人觉得有意思的内容1.redis的事务操作以及watch 乐观锁:后面描述2.tornado下异步使用redis的方式 ...
- 全面的Seo面试题
一.选择题(每题2分,2分×10=20分) 1.白帽SEO诞生日是: A .1997年9月15日 :B .2004年12月13日:C.2005年3月26日:D.2009年6月1日 2.一个做女性服 ...
- html初学者笔记01
一.Html简介 HTML 是一种标记语言 忽略大小写,语法宽松 使用 HTML 标记和元素,可以: 控制页面和内容的外观 发布联机文档 使用 HTML 文档中插入的链接检索联机信息 创建联机表单,收 ...
- spark job, stage ,task介绍。
1. spark 如何执行程序? 首先看下spark 的部署图: 节点类型有: 1. master 节点: 常驻master进程,负责管理全部worker节点. 2. worker 节点: 常驻wor ...
- Glide 加载图片
//通过model获取到图片的url,将Url转换成bitmap对象: //设置不保存内存和硬盘缓存, 1 Glide.with(mContext).load(model.getVideoUrl()) ...
- Android中Http加载如何得到Cookie和 WebView 加载网页如何得到的Cookie
最近做项目在手机端登录Http请求和 WebView 记载登录获取Cookie信息,可查看Cookie信息. 如图: Http请求获取Cookie信息: public static String re ...
- 使用Maven自动部署Java Web应用到Tomcat服务器
学习如何使用Maven,我推荐一本工具书,<maven the definitive guide>.在这本工具书手中,详细介绍了maven的使用思想,并且提供了从基本到复杂的具体项目应用. ...
- iOS 键盘类型
版权声明:本文为博主原创文章.请尊重作者劳动成果,转载请注明出处. UIKeyboardTypeDefault: UIKeyboardTypeASCIICapable: UIKeyboardTypeN ...
- jQuery Mobile 表单输入元素
jQuery Mobile 文本输入 输入字段是通过标准的 HTML 元素编写的,jQuery Mobile 会为它们设置专门针对移动设备的美观易用的样式.您还可以使用新的 HTML5 <inp ...
- 【随笔】Photoshop简单切图
一.首先准备一张需要切割的图: 二.打开Photoshop,在ps里打开这张图片: 三.在界面的左边选择切片工具: 四.根据需要,鼠标点住上方和左边的标尺拖动,给图片加上分割辅助线: 五.用切片工具点 ...