JAVA 类与构造方法
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
child() { co=new other(); system.out.println("childs default constructor"); } child(int m) { this(); cm1=m; system.out.println("childs self-define constructor"); } static void staticmethod() { system.out.println("childs staticmethod"); } int cmethod() { system.out.println("childs method"); return 3; } } class other { int om1; other() { system.out.println("others 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); sys来自百度文库em.out.println("program end"); } } 进入此文件所在的目录,然后 编译此文件:javac initializationtest.java 运行此程式:java ?classpath . initializationtest 得到的结果是: program start parents static initialize block childs static initialize block 10 parents method parents instance initialize block parents default constructor childs method childs instance initialize block others default constructor childs default constructor childs self-define constructor program end 如果没有看过上面的关于类的构造的说明,非常容易让人误解为类的构造顺序是如下的结果(忽略参数绑定、内存分 配和非静态成员的缺省值赋值):
关键字: java 构造 javap 字节码 bytecode 按照 java 规范,一个类实例的构造过程是遵循以下顺序的: 1.如果构造方法(constructor,也有翻译为构造器和构造函数的)是有参数的则进行参数绑定。 2.内存分配将非静态成员赋予初始值(原始类型的成员的值为规定值,例如 int 型为 0,float 型为 0.0f,boolean 型
4.执行显式的 super()调用(能是其他带参数的 super()调用)或隐式的 super()调用(缺省构造方法),此步骤又进 入一个父类的构造过程并一直上推至 object 对象的构造。
5.执行类申明中的成员赋值和初始化块。 6.执行构造方法中的其他语句。 目前来看看精心构造的一个实例: class parent { int pm1; int pm2=10; int pm3=pmethod(); { system.out.println("parents instance initialize block"); } public static int spm1=10; static { system.out.println("parents static initialize block"); } parent() { system.out.println("parents default constructor"); } static void staticmethod() { system.out.println("parents staticmethod"); } int pmethod() { system.out.println("parents method"); return 3; } } class child extends parent { int cm1; int cm2=10;
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 "childs 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 "childs 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 "childs 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 "childs self-define constructor"> 14 invokevirtual #21 <method void println(java.lang.string)>
int cm3=cmethod(); other co; public static int scm1=10; { system.out.println("childs instance initialize block"); } static { system.out.println("childs static initialize block"); }
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;
为 false;对象类型的初始值为 null),静态成员是属于类对象而非类实例,所以类实例的生成不进行静态成员的构造或 初始化,后面将讲述静态成员的生成时间。
3.如果构造方法中存在 this()调用(能是其他带参数的 this()调用)则执行之,执行完毕后进入第 6 步继续执行,如 果没有 this 调用则进行下一步。
17 return method int cmethod() 0 getstatic #20 <field java.io.printstream out> 3 ldc #3 <string "childs 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 "childs staticmethod"> 5 invokevirtual #21 <method void println(java.lang.string)> 8 return 请仔细浏览一下这个输出并和原始码比较一下。 下面解释怎么根据这个输出得到类实例的实际的构造顺序,在开始说明前先解释一下输出的语句的格式,语句中最 前面的一个数字是指令的偏移值,这个我们在此能不管,第二项是指令助记符,能从字面上大致看出指令的意思。 例如 getstatic 指令将一个静态成员压入一个称为操作数堆栈(后续的指令就能引用这个数据结构中的成员)的数 据结构,而 invokevirtual 指令是调用 java 虚拟机方法,第三项是操作数(#号后面跟一个数字,实际上是类的成员的 标记),有些指令没有这一项,因为有些指令如同汇编指令中的某些指令相同是不必操作数的(可能是操作数是隐含的 或根本就不必),这是 java 中的一个特色。 如果你直接检查字节码,你会看到成员信息没有直接嵌入指令而是像所有由 java 类使用的常量那样存储在一个共享 池中,将成员信息存储在一个常量池中能减小字节码指令的大小,因为指令只需要存储常量池中的一个索引而不是整个 常量。 需要说明的是常量池中的项目的顺序是和编译器相关的,因此在你的环境中看到的可能和我上面给出的输出不完全 相同,第四项是对前面的操作数的说明,实际的字节码中也是没有的,根据这个你能非常清晰的得到实际上使用的是哪 个成员或调用的是哪个方法,这也是 javap 为我们提供的便利。
说完上面这些你目前应该非常容易看懂上面的结果和下面将要叙述的内容了。其他更进一步的有关 java 字节码的信 息请自己查找资料。
先看看最开始的部分,非常像一个标准的 c++类的声明,确实如此。成员声明的后面没有了成员初始化赋值语句和初 始化块,那么这些语句何时执行的呢?先不要急,继续往下看。
system.out.println("program start"); system.out.println(child.scm1); c= new child(10); sys来自百度文库em.out.println("program end"); } } 进入此文件所在的目录,然后 编译此文件:javac initializationtest.java 运行此程式:java ?classpath . initializationtest 得到的结果是: program start parents static initialize block childs static initialize block 10 parents method parents instance initialize block parents default constructor childs method childs instance initialize block others default constructor childs default constructor childs self-define constructor program end 如果没有看过上面的关于类的构造的说明,非常容易让人误解为类的构造顺序是如下的结果(忽略参数绑定、内存分 配和非静态成员的缺省值赋值):
关键字: java 构造 javap 字节码 bytecode 按照 java 规范,一个类实例的构造过程是遵循以下顺序的: 1.如果构造方法(constructor,也有翻译为构造器和构造函数的)是有参数的则进行参数绑定。 2.内存分配将非静态成员赋予初始值(原始类型的成员的值为规定值,例如 int 型为 0,float 型为 0.0f,boolean 型
4.执行显式的 super()调用(能是其他带参数的 super()调用)或隐式的 super()调用(缺省构造方法),此步骤又进 入一个父类的构造过程并一直上推至 object 对象的构造。
5.执行类申明中的成员赋值和初始化块。 6.执行构造方法中的其他语句。 目前来看看精心构造的一个实例: class parent { int pm1; int pm2=10; int pm3=pmethod(); { system.out.println("parents instance initialize block"); } public static int spm1=10; static { system.out.println("parents static initialize block"); } parent() { system.out.println("parents default constructor"); } static void staticmethod() { system.out.println("parents staticmethod"); } int pmethod() { system.out.println("parents method"); return 3; } } class child extends parent { int cm1; int cm2=10;
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 "childs 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 "childs 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 "childs 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 "childs self-define constructor"> 14 invokevirtual #21 <method void println(java.lang.string)>
int cm3=cmethod(); other co; public static int scm1=10; { system.out.println("childs instance initialize block"); } static { system.out.println("childs static initialize block"); }
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;
为 false;对象类型的初始值为 null),静态成员是属于类对象而非类实例,所以类实例的生成不进行静态成员的构造或 初始化,后面将讲述静态成员的生成时间。
3.如果构造方法中存在 this()调用(能是其他带参数的 this()调用)则执行之,执行完毕后进入第 6 步继续执行,如 果没有 this 调用则进行下一步。
17 return method int cmethod() 0 getstatic #20 <field java.io.printstream out> 3 ldc #3 <string "childs 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 "childs staticmethod"> 5 invokevirtual #21 <method void println(java.lang.string)> 8 return 请仔细浏览一下这个输出并和原始码比较一下。 下面解释怎么根据这个输出得到类实例的实际的构造顺序,在开始说明前先解释一下输出的语句的格式,语句中最 前面的一个数字是指令的偏移值,这个我们在此能不管,第二项是指令助记符,能从字面上大致看出指令的意思。 例如 getstatic 指令将一个静态成员压入一个称为操作数堆栈(后续的指令就能引用这个数据结构中的成员)的数 据结构,而 invokevirtual 指令是调用 java 虚拟机方法,第三项是操作数(#号后面跟一个数字,实际上是类的成员的 标记),有些指令没有这一项,因为有些指令如同汇编指令中的某些指令相同是不必操作数的(可能是操作数是隐含的 或根本就不必),这是 java 中的一个特色。 如果你直接检查字节码,你会看到成员信息没有直接嵌入指令而是像所有由 java 类使用的常量那样存储在一个共享 池中,将成员信息存储在一个常量池中能减小字节码指令的大小,因为指令只需要存储常量池中的一个索引而不是整个 常量。 需要说明的是常量池中的项目的顺序是和编译器相关的,因此在你的环境中看到的可能和我上面给出的输出不完全 相同,第四项是对前面的操作数的说明,实际的字节码中也是没有的,根据这个你能非常清晰的得到实际上使用的是哪 个成员或调用的是哪个方法,这也是 javap 为我们提供的便利。
说完上面这些你目前应该非常容易看懂上面的结果和下面将要叙述的内容了。其他更进一步的有关 java 字节码的信 息请自己查找资料。
先看看最开始的部分,非常像一个标准的 c++类的声明,确实如此。成员声明的后面没有了成员初始化赋值语句和初 始化块,那么这些语句何时执行的呢?先不要急,继续往下看。