李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
构造器内部多态方法的行为
Leefs
2019-10-15 AM
1561℃
0条
## 构造器内部多态方法的行为 ### 前言 关于《Java编程思想》的探索之路..... > 探讨该问题时对构造器调用顺序进行一下梳理: 1. **1.调用基类构造器 ,不断递归下去, 首先是构造这种层次结构的根,然后是下一层导出类,等等,直到最低层的导出类;** 2. **2.按声明的顺序调用成员的初始化方法;** 3. **3.调用导出类构造器的主体。** ### 一、构造器层次结构的调用 如果在一个构造器的内部调用正在构造的对象的某个动态绑定的方法,会发生什么有趣的事情? 在一般的方法内部,**动态绑定**的方法的调用是在运行时才决定的,对象在程序运行之前无从得知它自己到底是基类的对象,还是某个导出类的对象。如果在基类的构造器内部调用某个动态绑定方法,并且该方法是被导出类覆盖的,那么这便可能产生难以预料的后果,因为该导出类的对象还未被完全构造,但它的方法却被调用了。 > 下面我们通过一个示例来看一下问题所在: ```java class Glyph { void draw() { System.out.println("Glyph.draw()"); } Glyph() { System.out.println("Glyph() before draw()"); draw(); System.out.println("Glyph() after draw()"); } } class RoundGlyph extends Glyph { private int radius = 1; RoundGlyph(int r) { radius = r; System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius); } void draw() { System.out.println("RoundGlyph.draw(), radius = " + radius); } } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); } } ``` 运行结果: ```java Glyph() before draw() RoundGlyph.draw(), radius = 0 Glyph() after draw() RoundGlyph.draw(), radius = 5 ``` > 我们现在分析一下代码的具体实现过程: 1. 1.主函数中执行 **new RoundGlyph(5);** 2. 2.首先,访问父类**Glyph**的构造函数,执行 **System.out.println("Glyph() before draw()");**输出**Glyph() before draw()**。 3. 3.然后执行**Glyph**的构造函数中的draw(),这时会调用子类**RoundGlyph**覆写的draw()方法,此时子类构造函数还未被完全构造,radius还未被赋初值,只能是默认值0,因此输出**RoundGlyph.draw(), radius = 0**。 4. 4.继续执行父类构造函数的**System.out.println("Glyph() after draw()");**,输出**Glyph() after draw()**。 5. 5.父类的构造函数执行结束,接着执行子类 RoundGlyph的构造函数 ,**radius = r;System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);** 给radius赋值5,后输出 **RoundGlyph.draw(), radius = 5**。 通过上面的执行过程我们可以知道,父类**Glyph**的构造器中调用了已经被子类**RoundGlyph**覆写的draw()方法,并且将redius初始值设置成了0(系统所分配的默认值),这个显然和我们想要的初值为“1”不同。 我们在创建子类**RoundGlyph**对象时会先调用父类**Glyph**的构造器构造父类对象,而在父类的构造器中却调用了被子类覆盖的动态绑定的方法**draw()**,而这个方法所操纵的可能是子类中的还未进行初始化的成员**radius**。 这时候我们就应该感到疑惑,编译器为什么 没有报错,而是正常执行了? 前面我们讲述的初始化顺序并不是十分完整,而缺少的一步正是解答这个问题的关键所在。 > 我们在来看一下初始化的实际过程: 1. **1.在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零。** 2. **2.调用基类构造器 ,不断递归下去, 首先是构造这种层次结构的根,然后是下一层导出类,等等,直到最低层的导出类;** 3. **3.按声明的顺序调用成员的初始化方法;** 4. **4.调用导出类构造器的主体。** 这样做可以保证所有东西至少初始化成零(或者是某些特殊数据类型中与“零”等价的值),而不是仅仅留作垃圾。 **编译构造器时有一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话避免调用其它方法”。在构造器内唯一能够安全调用的方法是基类中的final方法(也适用于private方法它们自动属于final方法)。这些方法不能被覆盖。**
标签:
Java
,
Java编程思想
,
JavaSE
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://www.lilinchao.com/archives/9.html
上一篇
MySQL基本操作
下一篇
协变返回类型
取消回复
评论啦~
提交评论
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
标签云
Spring
MyBatis
Sentinel
容器深入研究
SpringCloud
Elasticsearch
Spark SQL
LeetCode刷题
SQL练习题
MyBatis-Plus
Spark
机器学习
Java阻塞队列
CentOS
Stream流
二叉树
数学
Nacos
查找
VUE
FastDFS
Livy
JavaSE
Java编程思想
排序
GET和POST
Golang基础
随笔
线程池
Yarn
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞