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

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

 1 package com.test.zj;
 2
 3 public class PolyConstructors {
 4
 5     public static void main(String[] args) {
 6         // TODO Auto-generated method stub
 7         new RoundGlyph(5);
 8     }
 9
10 }
11
12 class RoundGlyph extends Glyph {
13     private int radius = 1;
14
15     public RoundGlyph(int r) {
16         // TODO Auto-generated constructor stub
17         radius = r;
18         System.out.println("RoundGlyph radius==" + radius);
19     }
20
21     @Override
22     void draw() {
23         // TODO Auto-generated method stub
24         System.out.println("RoundGlyph draw() radius==" + radius);
25     }
26
27 }
28
29 class Glyph {
30     void draw() {
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

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

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

 1 package com.test.zj;
 2
 3 public class SuperClass
 4 {
 5     private int superValue;
 6
 7     public SuperClass()
 8     {
 9         setSuperValue(100);
10
11     }
12
13     public void setSuperValue(int x)
14     {
15         superValue=x;
16     }
17
18 }

然后我们定义它的子类:

 1 //这个子类继承自父类superclass
 2 public class SubClass extends SuperClass
 3 {
 4     private int subValue=10;
 5
 6     public SubClass()
 7     {
 8
 9     }
10     //这个方法重写了父类的方法
11     public void setSuperValue(int x)
12     {
13         //先调用父类的方法
14         super.setSuperValue(x);
15         //然后把值赋给自己的变量
16         subValue=x;
17
18     }
19
20     public void printSubValue()
21     {
22         System.out.println("subclass subvalue=="+subValue);
23     }
24
25 }

最后写个main函数 就可以了

 1 package com.test.zj;
 2
 3 public class MainClass {
 4
 5     public static void main(String[] args) {
 6         // TODO Auto-generated method stub
 7         SubClass sc=new SubClass();
 8         sc.printSubValue();
 9     }
10
11 }

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

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

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

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

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

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

先执行的是这个:

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

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

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

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

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

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

输出的值当然是0!

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

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

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

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

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

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

subValue的值都必定为10.

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

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

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

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

这个方法干嘛的:

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

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

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

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

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

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

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

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

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

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

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

时间: 2024-12-14 06:26:58

java 构造函数内部的多态方法 完全剖析的相关文章

Java构造器内部的多态方法

本文主要详解java构造器内部的多态方法,更多Java技术知识,请登陆疯狂软件教育官网. 我们知道,动态绑定的调用是在运行时才决定的,对象无法知道到底调用的是哪个类的方法. 当我们在构造器中调用动态绑定的方法,就会用到该方法被覆盖之后的定义.但是这种调用的效果难以预计,因为被覆盖的方法在对象被完全构造之前就会被调用.我们先来看看下面这段代码: Java代码 class Base{ private String name = "base"; Base(){ tellName(); pri

Java构造器内部的多态方法的行为

Java构造器调用的层次结构带来了一个有趣的两难问题.如果在一个构造器的内部调用正在构造的对象的某个动态绑定方法,那会发生什么情况? class Glyph{ void draw(){System.out.println("Glyph.draw()");} Glyph() { System.out.print("Glyph() before draw()"); draw(); System.out.println("Glyph() after draw()

构造器内部的多态方法的行为

每天发出自己学习的内容,坚持坚持... 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{

构造器内部的多态方法的行为详解(附源码)

前言 构造器调用的层次结构带来了一个有趣的两难问题.如果在一个构造器的内部调用正在构造的对象的某个动态绑定方法,那会发生什么情况呢?在一般的方法内部,动态绑定的调用是在运行时才决定的,因为对象无法知道它是属于方法所在的那个类,还是属于那个类的导出类. 如果要调用构造器内部的一个动态绑定方法,就要用到那个方法的被覆盖后的定义.然而,这个调用的效果可能相当难于预料,因为被覆盖的方法在对象被完全构造之前就会被调用.这可能会造成一些难于发现的隐藏错误. 从概念上讲,构造器的工作实际上是创建对象(这并非是

JAVA编程思想(4) - 多态(三)

若干个对象共享 例如Frog对象拥有其自己的对象,并且知道他们的存活多久,因为Frog对象知道何时调用dispose()去释放其对象.然而,如果这些成员对象中存在于其他一个或多个对象共享的情况,问题将不再简单,不再能简单的调用dispose()了.在这种情况下,我们也许需要引用计数来跟踪依旧访问着共享对象的数量. //: polymorphism/ReferenceCounting.java // Cleaning up shared member objects. import static

定义在构造函数内部的方法,会在它的每一个实例上都克隆这个方法;定义在构造函数的prototype属性上的方法会让它的所有示例都共享这个方法,但是不会在每个实例的内部重新定义这个方法. 如果我们的应用需要创建很多新的对象,并且这些对象还有许多的方法,为了节省内存,我们建议把这些方法都定义在构造函数的prototype属性上。

定义在构造函数内部的方法,会在它的每一个实例上都克隆这个方法;定义在构造函数的prototype属性上的方法会让它的所有示例都共享这个方法,但是不会在每个实例的内部重新定义这个方法. 如果我们的应用需要创建很多新的对象,并且这些对象还有许多的方法,为了节省内存,我们建议把这些方法都定义在构造函数的prototype属性上.当然,在某些情况下,我们需要将某些方法定义在构造函数中,这种情况一般是因为我们需要访问构造函数内部的私有变量.

理解java三大特性之多态

---恢复内容开始--- 面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承是为了重用父类代码.两个类若存在IS-A的关系就可以使用继承.,同时继承也为实现多态做了铺垫.那么什么是多态呢?多态的实现机制又是什么?请看我一一为你揭开: 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间

java类,对象,方法

1,类和对象   在面向对象的概念当中,类(class)是既包括数据又包括作用于数据的一组操作的封装体.类的数据称为成员变量,类对数据的操作称为成员方法.成员变量反映类的状态和特征,成员方法反映类的行为和能力.类的成员变量和方法统称为类的成员.   对象(Object)是类的实例(instance).对象是动态的,拥有生命周期,都会经历一个从创建.运行到消亡的过程.对象与类的关系就像变量与数据类型一样.   类声明 { 修饰符 } class <Classname> { extends <

【转】java提高篇之理解java的三大特性——多态

面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承是为了重用父类代码.两个类若存在IS-A的关系就可以使用继承.,同时继承也为实现多态做了铺垫.那么什么是多态呢?多态的实现机制又是什么?请看我一一为你揭开: 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底