Java重写方法与初始化的隐患

发表于:2015-11-27 09:24

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:sky198912    来源:51Testing软件测试网采编

#
java
#
Java
#
JAVA
分享:
  问题
  简单还原一下问题, 我们有一个类SuperClass
  public class SuperClass {
  private int mSuperX;
  public SuperClass() {
  setX(99);
  }
  public void setX(int x) {
  mSuperX = x;
  }
  }
  现在我们想随时知道mSuperX是什么值, 不用反射, 因为父类从不直接修改mSuperX的值, 总是通过setX来改, 那么最简单的方法就是继承SuperClass, 重写setX方法, 监听它的改变就好.下面是我们的子类SubClass:
  public class SubClass extends SuperClass {
  private int mSubX = 1;
  public SubClass() {}
  @Override
  public void setX(int x) {
  super.setX(x);
  mSubX = x;
  System.out.println("SubX is assigned " + x);
  }
  public void printX() {
  System.out.println("SubX = " + mSubX);
  }
  }
  我使用mSubX来跟踪mSuperX
  因为在ViewGroup中, clipToPadding默认值是true(为了简化问题, 把它当成boolean, 实际并不是), 而ViewGroup初始化有可能不调用setClipToPadding, 此时是默认值, 为了模拟这种情况, 将mSubX初始化为1.
  最后在main里调用:
  public class Main {
  public static void main(String[] args) {
  SubClass sc = new SubClass();
  sc.printX();
  }
  }
  很多人, 包括我, 认为终端输出的结果应该是:
  SubX is assigned 99
  SubX = 99
  然而真正运行后输出的是:
  SubX is assigned 99
  SubX = 1
  实际分析
  要想知道发生了什么, 最简单的方法就是看看到底程序到底是怎么执行的, 比如单步调试, 或者直接一点, 看看Java字节码.
  下面是Main的字节码
  Compiled from "Main.java"
  public class bugme.Main {
  ......
  public static void main(java.lang.String[]);
  Code:
  0: new           #2                  // class bugme/SubClass
  3: dup
  4: invokespecial #3                  // Method bugme/SubClass."<init>":()V
  ......
  }
  这是直接用javap反编译.class文件得到的. 虽说同样是Java写的, 用apktool反编译APK文件(其中的dex文件)得到的smali代码和Java Bytecode明显长得不一样.
  字节码乍一看怪怪的, 只要知道它隐含了一个栈和局部变量表就好懂了.
  这段代码首先new一个SubClass实例, 把引用入栈, dup是把栈顶复制一份入栈, invokespecial #3将栈顶元素出栈并调用它的某个方法, 这个方法具体是什么要看常量池里第3个条目是什么, 但是javap生成的字节码直接给我们写在旁边了, 即SubClass.<init>.
  接下来看SubClass.<init>,
  public class bugme.SubClass extends bugme.SuperClass {
  public bugme.SubClass();
  Code:
  0: aload_0
  1: invokespecial #1                  // Method bugme/SuperClass."<init>":()V
  ......
  这里面并没有方法叫<init>, 是因为javap为了方便我们阅读, 直接把它改成类名bugme.SubClass, 顺便一提, bugme是包名. <init>方法并非通常意义上的构造方法, 这是Java帮我们合成的一个方法, 里面的指令会帮我们按顺序进行普通成员变量初始化, 也包括初始化块里的代码, 注意是按顺序执行, 这些都执行完了之后才轮到构造方法里代码生成的指令执行. 这里aload_0将局部变量表中下标为0的元素入栈, 其实就是Java中的this, 结合invokespecial #1, 是在调用父类的构造函数, 也就是我们常见的super().
  所以我们再看SuperClass.<init>
  public class bugme.SuperClass {
  public bugme.SuperClass();
  Code:
  0: aload_0
  1: invokespecial #1                  // Method java/lang/Object."<init>":()V
  4: aload_0
  5: bipush        99
  7: invokevirtual #2                  // Method setX:(I)V
  10: return
  ......
  }
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号