java 构造函数内部的多态方法 完全剖析
我们先来看一个例子,如果你读过《java编程思想》的话 应该会有印象
package com.test.zj; public class PolyConstructors { public static void main(String[] args) {
// TODO Auto-generated method stub
new RoundGlyph(5);
} } class RoundGlyph extends Glyph {
private int radius = 1; public RoundGlyph(int r) {
// TODO Auto-generated constructor stub
radius = r;
System.out.println("RoundGlyph radius==" + radius);
} @Override
void draw() {
// TODO Auto-generated method stub
System.out.println("RoundGlyph draw() radius==" + radius);
} } class Glyph {
void draw() {
System.out.println("print glyph.draw()");
} Glyph() {
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()"); } }
对于java基础一般的同学来说 这里你可能会认为输出是如下:
Glyph() before draw()
RoundGlyph draw() radius==1
Glyph() after draw()
RoundGlyph radius==5
但实际上你运行完毕以后 你会发现他的输出是这样的:
可能有的人读到这里还是不太明白我要表述什么,那我再写一个简单的例子。先定义一个父类SuperClass
package com.test.zj; public class SuperClass
{
private int superValue; public SuperClass()
{
setSuperValue(100); } public void setSuperValue(int x)
{
superValue=x;
} }
然后我们定义它的子类:
//这个子类继承自父类superclass
public class SubClass extends SuperClass
{
private int subValue=10; public SubClass()
{ }
//这个方法重写了父类的方法
public void setSuperValue(int x)
{
//先调用父类的方法
super.setSuperValue(x);
//然后把值赋给自己的变量
subValue=x; } public void printSubValue()
{
System.out.println("subclass subvalue=="+subValue);
} }
最后写个main函数 就可以了
package com.test.zj; public class MainClass { public static void main(String[] args) {
// TODO Auto-generated method stub
SubClass sc=new SubClass();
sc.printSubValue();
} }
好,现在我相信很多人都会认为第二个例子输出的结果应该是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 找了好久 发现是这个原因啊!所以以后你们发现有人这么写,请直接写邮件抄送全组投诉他啊!
java 构造函数内部的多态方法 完全剖析的更多相关文章
- [Java]构造函数内部多态的行为所引起的灾难
构造函数内部的多态行为所产生的意想不到的结果 一.Java版本 1 package com.company; 2 import static com.brianyi.util.Print.*; 3 4 ...
- Java编程思想之八多态
在面向对象的程序设计语言中,多态是继数据和继承之后的第三张基本特征 多态不但能够改善代码组织结构和可读性,还能够创建可扩展的程序--即无论在项目最初创建时还是在需要添加新功能时都可以"生长& ...
- 《java编程思想》多态与接口
向上转型 定义:把某个对象的引用视为对其基类类型的引用的做法被称为向上转型方法调用绑定 将一个方法调用同一个方法主体关联起来被称作绑定. 前期绑定:程序执行前进行的绑定叫做前期绑定,前期绑定也是jav ...
- java基础篇 之 构造器内部的多态行为
java基础篇 之 构造器内部的多态行为 我们来看下下面这段代码: public class Main { public static void main(String[] args) { new ...
- JAVA构造函数(方法)
一.什么是构造函数 java构造函数,也叫构造方法,是java中一种特殊的函数.函数名与相同,无返回值. 作用:一般用来初始化成员属性和成员方法的,即new对象产生后,就调用了对象了属性和方法. 在现 ...
- Java中的多态方法
public class Main { public void test(Object o) { System.out.println("Object"); } public vo ...
- java 实验3 继承+多态
实验3 继承与多态 **类可以实现多个接口 但只有单继承!** 1.继承 1).继承语法 class 子类名 extends 父类{ } 2).构造函数(通过source即可得到) 注意: ...
- java提高篇之理解java的三大特性——多态
面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承 ...
- [C#解惑] #1 在构造函数内调用虚方法
谜题 在C#中,用virtual关键字修饰的方法(属性.事件)称为虚方法(属性.事件),表示该方法可以由派生类重写(override).虚方法是.NET中的重要概念,可以说在某种程度上,虚方法使得多态 ...
随机推荐
- Linux之select系统调用_2
在上一篇博文中,我们的程序中我们有3个客户端,因此也事先建立了3个管道,每个客户端分别使用一个管道向服务器发送消息.而在服务器端使用select系统调用,只要监测到某一管道有消息写入,服务器就将其re ...
- java for循环的几种写法
J2SE 1.5提供了另一种形式的for循环.借助这种形式的for循环,可以用更简单地方式来遍历数组和Collection等类型的对象.本文介绍使用这种循环的具体方式,说明如何自行定义能被这样遍历的类 ...
- hdu 1567 2006 (题意理解容易出错)
#include<stdio.h> #include<vector> #include<algorithm> using namespace std; struct ...
- Linux进程调度原理
Linux进程调度原理 Linux进程调度机制 Linux进程调度的目标 1.高效性:高效意味着在相同的时间下要完成更多的任务.调度程序会被频繁的执行,所以调度程序要尽可能的高效: 2.加强交互性能: ...
- Android核心分析之十七电话系统之rilD
Android电话系统之-rild Rild是Init进程启动的一个本地服务,这个本地服务并没有使用Binder之类的通讯手段,而是采用了socket通讯这种方式.RIL(Radio Interfac ...
- Hibernate笔记——C3P0配置
Hibernate作为持久层(ORM)框架,操作数据库,自然也就离不开数据库连接池了.其支持多种连接池,这里就用最熟悉的C3P0连接池. C3P0连接池前面已经介绍了并使用很多次了就不再详细说明了. ...
- Help Jimmy--poj1661(dp)
题目链接:http://poj.org/problem?id=1661 下图是左边的,右边的同理: #include<stdio.h> #include<string.h> # ...
- Java:装饰设计模式
装饰设计模式: 当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能, 并提供加强功能,那么自定义的该类就称为装饰类. 装饰类通常通过构造方法接收被装饰的对象,并基于被装饰的对 ...
- Git设置及GitHub的使用
把github上的help略略翻译一遍.备忘. First : 安装:ubuntu 下,终端输入命令: sudo apt-get install git-core git-gui git-doc Ne ...
- Lucene学习笔记(更新)
1.Lucene学习笔记 http://www.cnblogs.com/hanganglin/articles/3453415.html