您的位置:首页 > 编程语言 > Java开发

Java对象实例化过程分析

2015-04-30 17:50 281 查看
(一) 问题描述

最近,同事遇到一个关于java 初始化的问题,描述如下:

子类构造器中调用super(),然后在父类构造器中调用子类有@overwrite的方法,子类在overwrite的方法中对自己成员赋值,log输出成功赋值,在子类new完,log打印发现部分成员变量值丢失了。








打印log发现list数据丢失了,int值还在。

乍看起来,很奇怪吧? 其实是因为对初始化的一些问题没有理解清楚,导致了数据丢失。初始化问题算是java中比较初级的问题,但也能看出知识点掌握的深不深、牢不牢

(二) 结论

先给出结论,关于对象实例化的顺序,简易描述如下:

先静态,再动态;
先父类,再子类;
先动态字段,再构造函数;
先静态字段,再静态块;

所以,造成上面结果的原因就是,先父类,再子类,在父类的构造函数里调用子类的read初始化后,子类又会进行初始化,list又被重新初始化了。面intVlaue为什么没有初始化?发现非静态成员,如果没有显式初始化赋值的话,相应类型的数据有默认的初始值(int类型为0,引用类型为null)。在类非静态成员初始化过程中,不会对这些值显示赋值,所以上面例子中intValue在父类构造器中赋值后,数据能够保留下来。so, 例子中的bug, 简单的解决办法还可以直接把定义地方的显示赋值去掉就ok了

如果只关注结果或结论,喜欢硬背的话,节省时间,下面可以不用看。 喜欢刨根问底的可以继续看下去,相信会理解的更好!

----------------------------------------------------------------------------------------------------

(三) 结论理解 与 分析

此节分析,上面的那段顺序为什么为是那样的顺序。让我们来分析和理解一下。

1 先静态,再动态。 静态字段是关于类的, 在 ClassLoader加载类的时候就初始化了所以静态的初始化的很早 很早

2 先父类,再子类。 是常识。 但是实现的原理,是在java编译成字节码时, 如果是默认构造函数(如 A() 或不写构造函数时 ),编译器会在编译成的字节码里 会自动加上 super(); 所以就先初始父了。 如果父类构造函数不是默认的,会编译出错,让你手动加上super(a,b)

3 先动态字段,再构造函数; 字节码角度实现上,在字节码中,会把 字段的初始化 放到字节码中的构造函数的最前面。不信来验证一下:

简单类
一个Test中的内部类CC

public static class CC {
   String c1 = "ccccc";
   public CC() {
     c1 = "cccc2";
  }
  void test() {
  }
}


编译后看字节码:用javap 命令可以看

Compiled from "Test.java"
public class Test$CC {
  java.lang.String c1;

  public Test$CC(java.lang.String);
    Code:
       0: aload_0
       1: invokespecial #10                 // Method
java/lang/Object."<init>":()V
       4: aload_0
       5: ldc           #13                 // String ccccc
       7: putfield      #15                 // Field c1:Ljava/lang/String;
      10: aload_0
      11: ldc           #17                 // String cccc2
      13: putfield      #15                 // Field c1:Ljava/lang/String;
      16: return

  void test();
    Code:
       0: return
}


看 1:位置 会加上 object.init吧 即自动加上super了 说明了 规律2 先父类,再子类 的问题

7: 在初始化函数里 先字段初始化了 "c1"

11: 后又进行真正初始化了 "cccc2"

所以 先动态字段,再构造函数 就很好理解了

4 先静态字段,再静态块; 这个可以根据第3条规律类推了。 从反射上来讲 Class 对象也是个Object对象, static代码块 相当于Class对象的初始化函数,static字段相当于Class对象的字段。 而按3规律的实现上讲,字节码中 可能也是 把static字段的初始化放到static块中最前面了。 此处没有验证

所以实例化过程的 终极规律 就是 如果Class对象没有加载先加载并初始化Class, 再根据Class分配一段实际对象的初始状态的内存(int 字段为 0 object 为null , bool 为false ) 然后执行对象初始化函数 这个对象就产生了 !!!! 按字节码中初始化函数自然执行,最终就是(二)中所说的顺序。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: