服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

深入分析java中类的构造


  概要:本文通过查看一个精心构造的类结构的运行输出和使用javap工具查看实际生成的java字节码(bytecode)向java程序员展示了一个类在运行时是如何构造生成的。

  关键字: java 构造 javap 字节码 bytecode

  按照java规范,一个类实例的构造过程是遵循以下顺序的:

  1.如果构造方法(constructor,也有翻译为构造器和构造函数的)是有参数的则进行参数绑定。

  2.内存分配将非静态成员赋予初始值(原始类型的成员的值为规定值,例如int型为0,float型为0.0f,boolean型为false;对象类型的初始值为null),静态成员是属于类对象而非类实例,所以类实例的生成不进行静态成员的构造或者初始化,后面将讲述静态成员的生成时间。

  3.如果构造方法中存在this()调用(可以是其它带参数的this()调用)则执行之,执行完毕后进入第6步继续执行,如果没有this调用则进行下一步。

  4.执行显式的super()调用(可以是其它带参数的super()调用)或者隐式的super()调用(缺省构造方法),此步骤又进入一个父类的构造过程并一直上推至object对象的构造。

  5.执行类申明中的成员赋值和初始化块。

  6.执行构造方法中的其它语句。

  现在来看看精心构造的一个实例:


  class parent
  {
  int pm1;
  int pm2=10;
  int pm3=pmethod();
  {
  system.out.println("parent's instance initialize block");
  }
  public static int spm1=10;
  static
  {
  system.out.println("parent's static initialize block");
  }
  
  parent()
  {
  system.out.println("parent's default constructor");
  }
  static void staticmethod()
  {
  system.out.println("parent's staticmethod");
  }


 int pmethod()
  {
  system.out.println("parent's method");
  return 3;
  }
  }

  class child extends parent
  {
  int cm1;
  int cm2=10;
  int cm3=cmethod();
  other co;
  public static int scm1=10;
  {
  system.out.println("child's instance initialize block");
  }
  static
  {
  system.out.println("child's static initialize block");
  }
  
  child()
  {
  co=new other();
  system.out.println("child's default constructor");
  }
  child(int m)
  {
  this();
  cm1=m;
  system.out.println("child's self-define constructor");
  }
  static void staticmethod()
  {
  system.out.println("child's staticmethod");
  }
  
  int cmethod()
  {
  system.out.println("child's method");
  return 3;
  }

  }

  class other
  {
  int om1;
  other() {
  system.out.println("other's default constructor");
  }

  }
 public class initializationtest
  {
  public static void main(string args[])
  {
  child c;
  system.out.println("program start");
  system.out.println(child.scm1);
  c= new child(10);
  system.out.println("program end");
  }
  }





  进入此文件所在的目录,然后
  编译此文件:javac initializationtest.java
  运行此程序:java ?classpath . initializationtest
  得到的结果是:


  program start
  parent's static initialize block
  child's static initialize block
  10
  parent's method
  parent's instance initialize block
  parent's default constructor
  child's method
  child's instance initialize block
  other's default constructor
  child's default constructor
  child's self-define constructor
  program end




  如果没有看过上面的关于类的构造的说明,很容易让人误解为类的构造顺序是如下的结果(忽略参数绑定、内存分配和非静态成员的缺省值赋值):

  1.完成父类的非静态成员初始化赋值以及执行初始化块(这个的先后顺序取决于源文件中的书写顺序,可以将初始化块置于成员声明前,那么先执行的将是初始化块,将上面的代码稍稍变动一下就可以验证这一点。)

  2.调用父类的构造方法完成父类构造。

  3.完成非静态成员的初始化赋值以及执行初始化块。

  4.调用构造方法完成对象的构造,执行构造方法体中的其它内容。

  如果根据以上java规范中给出的顺序也可以合理的解释程序的输出结果,那么如何亲眼看到是规范中的顺序而不是以上根据程序的输出推断的顺序呢?
  下面就使用jdk自带的javap工具看看实际的顺序,这个工具是一个根据编译后的字节码生成一份字节码的助记符格式的文档的工具,就像根据机器码生成汇编代码那样。


反编译:javap -c -classpath . child
  输出的结果是(已经经过标记,交替使用黑体和斜体表示要讲解的每一块):


  compiled from initializationtest.java
  class child extends parent {
  int cm1;
  int cm2;
  int cm3;
  other co;
  public static int scm1;
  static {};
  child();
  child(int);
  int cmethod();
  static void staticmethod();
  }

  method static {}
  0 bipush 10
  2 putstatic #22 <field int scm1>
  5 getstatic #20 <field java.io.printstream out>
  8 ldc #5 <string "child's static initialize block">
  10 invokevirtual #21 <method void println(java.lang.string)>
  13 return

  method child()
  0 aload_0
  1 invokespecial #14 <method parent()>
  4 aload_0
  5 bipush 10
  7 putfield #16 <field int cm2>
  10 aload_0
  11 aload_0
  12 invokevirtual #18 <method int cmethod()>
  15 putfield #17 <field int cm3>
  18 getstatic #20 <field java.io.printstream out>
  21 ldc #2 <string "child's instance initialize block">
  23 invokevirtual #21 <method void println(java.lang.string)>
  26 aload_0
  27 new #8 <class other>
  30 dup
  31 invokespecial #13 <method other()>
  34 putfield #19 <field other co>
  37 getstatic #20 <field java.io.printstream out>
  40 ldc #1 <string "child's default constructor">
  42 invokevirtual #21 <method void println(java.lang.string)>
  45 return

  method child(int)
  0 aload_0
  1 invokespecial #12 <method child()>
  4 aload_0
  5 iload_1


6 putfield #15 <field int cm1>
  9 getstatic #20 <field java.io.printstream out>
  12 ldc #4 <string "child's self-define constructor">
  14 invokevirtual #21 <method void println(java.lang.string)>
  17 return

  method int cmethod()
  0 getstatic #20 <field java.io.printstream out>
  3 ldc #3 <string "child's method">
  5 invokevirtual #21 <method void println(java.lang.string)>
  8 iconst_3
  9 ireturn

  method void staticmethod()
  0 getstatic #20 <field java.io.printstream out>
  3 ldc #6 <string "child's staticmethod">
  5 invokevirtual #21 <method void println(java.lang.string)>
  8 return





  请仔细浏览一下这个输出并和源代码比较一下。

  下面解释如何根据这个输出得到类实例的实际的构造顺序,在开始说明前先解释一下输出的语句的格式,语句中最前面的一个数字是指令的偏移值,这个我们在此可以不管,第二项是指令助记符,可以从字面上大致看出指令的意思。

  例如 getstatic 指令将一个静态

扫描关注微信公众号