当前位置: 首页 > news >正文

java 库 方法内部_java 构造函数内部的多态方法 完全剖析

我们先来看一个例子,如果你读过《java编程思想》的话 应该会有印象

1 packagecom.test.zj;2

3 public classPolyConstructors {4

5 public static voidmain(String[] args) {6 //TODO Auto-generated method stub

7 new RoundGlyph(5);8 }9

10 }11

12 class RoundGlyph extendsGlyph {13 private int radius = 1;14

15 public RoundGlyph(intr) {16 //TODO Auto-generated constructor stub

17 radius =r;18 System.out.println("RoundGlyph radius==" +radius);19 }20

21 @Override22 voiddraw() {23 //TODO Auto-generated method stub

24 System.out.println("RoundGlyph draw() radius==" +radius);25 }26

27 }28

29 classGlyph {30 voiddraw() {31 System.out.println("print glyph.draw()");32 }33

34 Glyph() {35 System.out.println("Glyph() before draw()");36 draw();37 System.out.println("Glyph() after draw()");38

39 }40

41 }

对于java基础一般的同学来说 这里你可能会认为输出是如下:

1 Glyph() before draw()2 RoundGlyph draw() radius==1

3 Glyph() after draw()4 RoundGlyph radius==5

但实际上你运行完毕以后 你会发现他的输出是这样的:

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

可能有的人读到这里还是不太明白我要表述什么,那我再写一个简单的例子。先定义一个父类SuperClass

1 packagecom.test.zj;2

3 public classSuperClass4 {5 private intsuperValue;6

7 publicSuperClass()8 {9 setSuperValue(100);10

11 }12

13 public void setSuperValue(intx)14 {15 superValue=x;16 }17

18 }

然后我们定义它的子类:

1 //这个子类继承自父类superclass

2 public class SubClass extendsSuperClass3 {4 private int subValue=10;5

6 publicSubClass()7 {8

9 }10 //这个方法重写了父类的方法

11 public void setSuperValue(intx)12 {13 //先调用父类的方法

14 super.setSuperValue(x);15 //然后把值赋给自己的变量

16 subValue=x;17

18 }19

20 public voidprintSubValue()21 {22 System.out.println("subclass subvalue=="+subValue);23 }24

25 }

最后写个main函数 就可以了

1 packagecom.test.zj;2

3 public classMainClass {4

5 public static voidmain(String[] args) {6 //TODO Auto-generated method stub

7 SubClass sc=newSubClass();8 sc.printSubValue();9 }10

11 }

好,现在我相信很多人都会认为第二个例子输出的结果应该是100

但其实并没有什么卵用,他的实际结果是:

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

那到底这两个例子都发生了什么呢,我们直接来看字节码好了,这个字节码肯定不会有错,字节码怎么写的 jvm就怎么执行。

我们就先看看第一个例子。

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

这里应该很明显的能看到 我们的main函数 一开始就是new了RoundGlyph这个对象。那我们看看这个类-c的结果吧

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

可以看到这个类的构造函数

先执行的是这个:

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

也就是说 先执行了glyph的构造方法 然后当glyph的构造函数执行完毕以后 才执行的赋值语句

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

我们的radius 作为一个int变量 在被执行之前 jvm自动初始化他的值为0!

所以你这里隐隐约约应该都能猜到一个大概了,先执行的glyph的 构造函数,然后再给自己的成员变量radius赋值。

那我们看看glyph 都做了什么吧:

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

你看glyph的构造函数, 在中间的时候13:invokevirtual #31 这里,去执行了draw方法,但是子类我们重写了这个draw方法

所以你看 在glyph的构造函数里 调用子类的draw方法的时候 子类的radius赋值语句并没有被执行到,所以子类的这个方法

输出的值当然是0!

当父类glyph的构造函数执行完毕以后 ,我们的子类的赋值语句才终于得到执行。所以到这里 你应该能明白第一个例子了。

那我们现在就可以去研究一下第二个例子,其实都是大同小异的。我们还是先看第二个例子的manclass和main函数

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

你看这里main函数 先是new了一个subclass 子类的对象 对吧。那我们当然就要去看看subclass init方法

实际上这个地方就是Subclass的构造函数了。

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

这里很清楚的可以看到 在subclass的构造函数里 我们是先执行的superclass的构造函数,然后才给自己的subValue赋值为10.

那我们就去看看superclass里都做了什么。但实际上走到这里我们已经能想到了无论你在superclass做了什么 当你做完以后

subValue的值都必定为10.

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

所以当你subclass的对象构造完毕以后 此时他的成员变量subvalue的值就是10了,所以你当然打印出来这个变量的值 就一定是10了。

当然为了更清晰一点 我还是把superclass构造函数里做了什么稍微讲一下,虽然这里面做了什么不会影响到我们的结论,但还是讲一下吧,

即使这并没有什么卵用。。。

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

你看这里就是调用了一下setSuperValue这个方法么,对吧,因为子类重写了这个方法 所以我们肯定要看看子类

这个方法干嘛的:

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

你看不就是又调用了父类的setSupervalue方法吗,然后调用以后 你看有个iload putfield

这2个操作不就是给我们子类的subvalue 赋值的吗,对吧。一直到这里,我们子类的对象构造函数的第一步:

调用父类的构造函数 就算是走完了,走完了以后 才终于执行了自己的赋值语句:

%E5%A4%96%E9%93%BE%E7%BD%91%E5%9D%80%E5%B7%B2%E5%B1%8F%E8%94%BD

好,这2个例子到这里就算分析完毕了。

实际上最终的结论就是java编程思想里说的那样:

父类static成员 -> 子类static成员 -> 父类普通成员初始化和初始化块 -> 父类构造方法 -> 子类普通成员初始化和初始化块 -> 子类构造方法

如果你们有兴趣的话,可以写一个稍微更复杂一点的程序,验证一下 上面的这个结论是否成立,废话。。。。这结论肯定是成立的。但是

你如果用javap -c 这个命令 去看他们的字节码的话 相信你能理解的更深了!

最后多说一句,平常我们在写代码的时候,尽量避免 上述2个例子这样的写法,因为这种情况造成的bug 很难被发现。。。即:

尽量不要在父类的构造函数里  操作子类的成员变量。如果一定要把初始化写的很麻烦的话,请考虑使用初始化块 这样一目了然的方法!

别问我为什么会研究到这,因为tmd 有一个bug 找了好久 发现是这个原因啊!所以以后你们发现有人这么写,请直接写邮件抄送全组投诉他啊!

相关文章:

  • 中国将承接世界文明
  • 浅谈持续集成CC
  • java架构和iis_防止IIS文件被下载方法
  • java数字循环求最小值_从键盘上接收一些数字,比较输出中的最大值和最小值,输入0结束循环。java写出来...
  • 持续集成工具CC介绍
  • java随机数_Java随机数Random()
  • Delphi调用WMI读取USB设备的PID和VID
  • java 计算器程序_java计算器程序
  • Log4j被OSGI撞了一下腰
  • 比较分析Vector、ArrayList和hashtable hashmap数据结构
  • java二进制类型_Java数据类型 - 原始和二进制文字
  • hibernate的Criteria的一个bug
  • toad导出mysql数据库_将toad里的数据库结构导出到pdm
  • 炒股精髓:多位高手多年心血结晶
  • 好神奇哟
  • 【前端学习】-粗谈选择器
  • Computed property XXX was assigned to but it has no setter
  • express + mock 让前后台并行开发
  • HTTP中的ETag在移动客户端的应用
  • Java到底能干嘛?
  • Mybatis初体验
  • mysql 5.6 原生Online DDL解析
  • npx命令介绍
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • windows-nginx-https-本地配置
  • 分享一份非常强势的Android面试题
  • 回顾2016
  • 那些年我们用过的显示性能指标
  • 深度解析利用ES6进行Promise封装总结
  • 智能合约Solidity教程-事件和日志(一)
  • 自定义函数
  • 最近的计划
  • gunicorn工作原理
  • # 执行时间 统计mysql_一文说尽 MySQL 优化原理
  • #DBA杂记1
  • (01)ORB-SLAM2源码无死角解析-(56) 闭环线程→计算Sim3:理论推导(1)求解s,t
  • (1)(1.9) MSP (version 4.2)
  • (5)STL算法之复制
  • (十八)三元表达式和列表解析
  • (算法)求1到1亿间的质数或素数
  • (原創) 物件導向與老子思想 (OO)
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (转)winform之ListView
  • (转)我也是一只IT小小鸟
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布
  • .net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别
  • .NET Core WebAPI中使用swagger版本控制,添加注释
  • .Net mvc总结
  • .NET Standard / dotnet-core / net472 —— .NET 究竟应该如何大小写?
  • .NET 表达式计算:Expression Evaluator
  • .Net 访问电子邮箱-LumiSoft.Net,好用
  • .Net6使用WebSocket与前端进行通信