• 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 找了好久 发现是这个原因啊!所以以后你们发现有人这么写,请直接写邮件抄送全组投诉他啊!

  • 相关阅读:
    手写token解析器、语法解析器、LLVM IR生成器(GO语言)
    Windows下切分文件(GnuWin32)
    转载:教你分分钟搞定Docker私有仓库Registry
    marathon传参一
    DC/OS安装
    自己写编程语言-m语言
    揽货最短路径解决方案算法
    揽货最短路径解决方案算法
    用keras作CNN卷积网络书本分类(书本、非书本)
    用keras做SQL注入攻击的判断
  • 原文地址:https://www.cnblogs.com/punkisnotdead/p/4936432.html
Copyright © 2020-2023  润新知