转贴自 http://hi.baidu.com/longchengjiang/blog/item/11918445dbddc125cffca3aa.html
++++++++++++++++++++++++++++ 构造器和方法的区别 ++++++++++++++++++++++++++++++++++++==
我们说构造器是一种方法,就象讲澳大利亚的鸭嘴兽是一种哺育动物。(按:老外喜欢打比喻,我也 就照着翻译)。要理解鸭嘴兽,那么先必须理解它和其他哺育动物的区别。同样地,要理解构造器,那么就要了解构造器和方法的区别。所有学习java的人,尤 其是对那些要认证考试的,理解构造器是非常重要的。下面将简单介绍一下 ,最后用一个表作了些简单的总结。
功能和作用的不同
构造器是为了创建一个类的实例。这个过程也可以在创建一个对象的时候用到:Platypus p1 = new Platypus();
相反,方法的作用是为了执行java代码。
修饰符,返回值和命名的不同
构造器和方法在下面三个方便的区别:修饰符,返回值,命名。和方法一样,构造器可以有任何访问 的修饰: public, protected, private或者没有修饰(通常被package 和 friendly调用). 不同于方法的是,构造器不能有以下非访问性质的修饰: abstract, final, native, static, 或者 synchronized。
返回类型也是非常重要的。方法能返回任何类型的值或者无返回值(void),构造器没有返回值,也不需要void。
最后,谈谈两者的命名。构造器使用和类相同的名字,而方法则不同。按照习惯,方法通常用小写字母开始,而构造器通常用大写字母开始。构造器通常是一个名词,因为它和类名相同;而方法通常更接近动词,因为它说明一个操作。
"this"的用法
构造器和方法使用关键字this有很大的区别。方法引用this指向正在执行方法的类的实例。 静态方法不能使用this关键字,因为静态方法不属于类的实例,所以this也就没有什么东西去指向。构造器的this指向同一个类中,不同参数列表的另 外一个构造器,我们看看下面的代码:
public class Platypus {
String name;
Platypus(String input) {
name = input;
}
Platypus() {
this("John/Mary Doe");
}
public static void main(String args[]) {
Platypus p1 = new Platypus("digger");
Platypus p2 = new Platypus();
}
}
在上面的代码中,有2个不同参数列表的构造器。第一个构造器,给类的成员name赋值,第二个构造器,调用第一个构造器,给成员变量name一个初始值 "John/Mary Doe".
在构造器中,如果要使用关键字this,那么,必须放在第一行,如果不这样,将导致一个编译错误。
"super"的用法
构造器和方法,都用关键字super指向超类,但是用的方法不一样。方法用这个关键字去执行被重载的超类中的方法。看下面的例子:
class Mammal {
void getBirthInfo() {
System.out.println("born alive.");
}
}
class Platypus extends Mammal {
void getBirthInfo() {
System.out.println("hatch from eggs");
System.out.print("a mammal normally is ");
super.getBirthInfo();
}
}
在上面的例子中,使用super.getBirthInfo()去调用超类Mammal中被重载的方法。
构造器使用super去调用超类中的构造器。而且这行代码必须放在第一行,否则编译将出错。看下面的例子:
public class SuperClassDemo {
SuperClassDemo() {}
}
class Child extends SuperClassDemo {
Child() {
super();
}
}
在上面这个没有什么实际意义的例子中,构造器 Child()包含了 super,它的作用就是将超类中的构造器SuperClassDemo实例化,并加到 Child类中。
编译器自动加入代码
编译器自动加入代码到构造器,对于这个,java程序员新手可能比较混淆。当我们写一个没有构造器的类,编译的时候,编译器会自动加上一个不带参数的构造器,例如:public class Example {}
编译后将如下代码:
public class Example {
Example() {}
}
在构造器的第一行,没有使用super,那么编译器也会自动加上,例如:
public class TestConstructors {
TestConstructors() {}
}
编译器会加上代码,如下:
public class TestConstructors {
TestConstructors() {
super;
}
}
仔细想一下,就知道下面的代码
public class Example {}
经过会被编译器加代码形如:
public class Example {
Example() {
super;
}
}
继承
构造器是不能被继承的。子类可以继承超类的任何方法。看看下面的代码:
public class Example {
public void sayHi {
system.out.println("Hi");
}
Example() {}
}
public class SubClass extends Example {
}
类 SubClass 自动继承了父类中的sayHi方法,但是,父类中的构造器 Example()却不能被继承。
总结
主题
构造器
方法
功能
建立一个类的实例
java功能语句
修饰
不能用bstract, final, native, static,
or synchronized
能
返回类型
没有返回值,没有void
有返回值,或者void
命名
和类名相同;通常为名词,大写开头
通常代表一个动词的意思,小写开头
this
指向同一个类中另外一个构造器,在第一行
指向当前类的一个实例,不能用于静态方法
super
调用父类的构造器,在第一行
调用父类中一个重载的方法
继承
构造器不能被继承
方法可以被继承
编译器自动加入一个缺省的构造器
自动加入(如果没有)
不支持
编译器自动加入一个缺省的调用到超类的构造器
自动加入(如果没有)
不支持
++++++++++++++++++++++++++JAVA技术专题综述之构造方法篇+++++++++++++++++++
示例1:
class SuperClass { SuperClass() { System.out.println("SuperClass constructor"); } } public class SubClass extends SuperClass { SubClass() { System.out.println("SubClass constructor"); } public static void main(String[] args) { SubClass sub = new SubClass(); } } 输出结果: SuperClass constructor SubClass constructor |
在子类中只实例化了一个子类对象。从输出结果上看,程序并不是一开始就运行自己的构造方法,而是先运行其父类的默认构造方法。注意:程序自动调用其父类的默认构造方法。
示例2:
class SuperClass { SuperClass(String str) { System.out.println("Super with a string."); } } public class SubClass extends SuperClass { SubClass(String str) { System.out.println("Sub with a string."); } public static void main(String[] args) { SubClass sub = new SubClass("sub"); } } |
在JDK下编译此程序不能成功。正如上例中说的:程序在初始化子类时先要寻找其父类的默认构造方法,结果没找到,那么编译自然不能通过。
解决这个问题有两个办法:
1.在父类中增加一个默认构造方法。
2.在子类的构造方法中增加一条语句:super(str); 且必须在第一句。
这两种方法都能使此程序通过编译,但就本程序来说运行结果却不相同。
第1种方法的运行结果是:
Sub with a string.
第2种方法的运行结果是:
Super with a string. Sub with a string. |
第2种解决方法实际上是指定编译器不要寻找父类的默认构造方法,而是去寻找带一个字符串为参数的构造方法。
下面介绍对象的初始化顺序问题。
示例3:
class One { One(String str) { System.out.println(str); } } class Two { One one_1 = new One("one-1"); One one_2 = new One("one-2"); One one_3 = new One("one-3"); Two(String str) { System.out.println(str); } } public class Test { public static void main(String[] args) { System.out.println("Test main() start..."); Two two = new Two("two"); } } 输出结果: Test main() start... one-1 one-2 one-3 two |
在main()方法中实例化了一个Two类的对象。但程序在初始化Two类的对象时,并非先调用Two类的构造方法, 而是先初始化Two类的成员变量。这里Two类有3个成员变量,它们都是One类的对象,所以要先调用3次One类的相应的构造方法。最后在初始化Two 类的对象。
示例4:
class One { One(String str) { System.out.println(str); } } class Two { One one_1 = new One("one-1"); One one_2 = new One("one-2"); static One one_3 = new One("one-3"); Two(String str) { System.out.println(str); } } public class Test { public static void main(String[] args) { System.out.println("Test main() start..."); Two two_1 = new Two("two-1"); System.out.println("------------"); Two two_2 = new Two("two-2"); } } 输出结果: Test main() start... one-3 one-1 one-2 two-1 ------------ one-1 one-2 two-2 |
如果一个类中有静态对象,那么它会在非静态对象前初始化,但只初始化一次。非静态对象每次调用时都要初始化。
示例5:
class One { One(String str) { System.out.println(str); } } class Two { One one_1 = new One("one-1"); One one_2 = new One("one-2"); static One one_3 = new One("one-3"); Two(String str) { System.out.println(str); } 3 } public class Test { static Two two_3 = new Two("two-3"); public static void main(String[] args) { System.out.println("Test main() start..."); Two two_1 = new Two("two-1"); System.out.println("------------"); Two two_2 = new Two("two-2"); } } 输出结果: one-3 one-1 one-2 two-3 Test main() start... one-1 one-2 two-1 ------------ one-1 one-2 two-2 |
程序中主类的静态变量会在main()方法执行前初始化。结果中只输出了一次one-3,这也说明:如果一个类中有静态对象,那么它会在非静态对象前初始化,但只初始化一次。非静态对象每次调用时都要初始化。
示例6:
class One { One(String str) { System.out.println(str); } } class Two { static int i = 0; One one_1 = new One("one-1"); static One one_2 = new One("one-2"); static One one_3 = new One("one-3"); Two(String str) { System.out.println(str); } } public class Test { public static void main(String[] args) { System.out.println("Test main() start..."); System.out.println("Two.i = " + Two.i); } } 4 输出结果: Test main() start... one-2 one-3 Two.i = 0 |
不仅第1次创建对象时,类中所有的静态变量要初始化,第1次访问类中的静态变量(没有创建对象)时,该类中所有的静态变量也要按照它们在类中排列的顺序初始化。
综上所述:
在创建对象时,对象所在类的所有数据成员会首先进行初始化,如果其中的成员变量有对象,那么它们也会按照顺序执行初始化工作。在所有类成员初始化完成后,才调用对象所在类的构造方法创建对象。构造方法作用就是初始化。
静态对象(变量)在非静态对象前初始化。静态对象(变量)只初始化一次,再次调用就不初始化了,但非静态对象在每次调用时都要初始化。
程序中的主类的静态变量会在main()方法执行前进行初始化工作。
不仅第1次创建对象时,类中所有的静态变量要初始化,第1次访问类中的静态变量(没有创建对象)时,该类中所有的静态变量也要按照它们在类中排列的顺序初始化。
初始化的顺序包括构造方法调用的顺序如下:
1.主类的静态成员首先初始化。
2.主类的超类的构造方法按照从最高到最低的顺序被调用。
3.主类的非静态对象(变量)初始化。
4.调用主类的构造方法。
在一个构造方法中只能调用一次其它的构造方法,并且调用构造方法的语句必须是第一条语句。
++++++++++++++++++++深入剖析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 pmethod() class Child extends Parent } class Other } |
public class InitializationTest |
进入此文件所在的目录,然后
编译此文件:javac InitializationTest.java
运行此程序:java ?classpath . InitializationTest
得到的结果是:
program start |
如果没有看过上面的关于类的构造的说明,很容易让人误解为类的构造顺序是如下的结果(忽略参数绑定、内存分配和非静态成员的缺省值赋值):
1.完成父类的非静态成员初始化赋值以及执行初始化块(这个的先后顺序取决于源文件中的书写顺序,可以将初始化块置于成员声明前,那么先执行的将是初始化块,将上面的代码稍稍变动一下就可以验证这一点。)
2.调用父类的构造方法完成父类构造。
3.完成非静态成员的初始化赋值以及执行初始化块。
4.调用构造方法完成对象的构造,执行构造方法体中的其它内容。
如果根据以上java规范中给出的顺序也可以合理的解释程序的输出结果,那么如何亲眼看到是规范中的顺序而不是以上根据程序的输出推断的顺序呢?
下面就使用JDK自带的javap工具看看实际的顺序,这个工具是一个根据编译后的字节码生成一份字节码的助记符格式的文档的工具,就像根据机器码生成汇编代码那样。
反编译:javap -c -classpath . Child
输出的结果是(已经经过标记,交替使用黑体和斜体表示要讲解的每一块):
Compiled from InitializationTest.java Method static {} Method Child() Method Child(int) |
6 putfield #15 <Field int cm1> Method int cmethod() Method void staticmethod() |
请仔细浏览一下这个输出并和源代码比较一下。
下面解释如何根据这个输出得到类实例的实际的构造顺序,在开始说明前先解释一下输出的语句的格式,语句中最前面的一个数字是指令的偏移值,这个我们在此可以不管,第二项是指令助记符,可以从字面上大致看出指令的意思。
例如 getstatic 指令将一个静态成员压入一个称为操作数堆栈(后续的指令就可以引用这个数据结构中的成员)的数据结构,而 invokevirtual 指令是调用java虚拟机方法,第三项是操作数(#号后面跟一个数字,实际上是类的成员的标记),有些指令没有这一项,因为有些指令如同汇编指令中的某些 指令一样是不需要操作数的(可能是操作数是隐含的或者根本就不需要),这是java中的一个特色。
如果你直接检查字节码,你会看到成员信息没有直接嵌入指令而是像所有由java类使用的常量那样存储在一个共享池中,将成员信息存储在一个常量池中可以减小字节码指令的大小,因为指令只需要存储常量池中的一个索引而不是整个常量。
需要说明的是常量池中的项目的顺序是和编译器相关的,因此在你的环境中看到的可能和我上面给出的输出不完全一样,第四项是对前面的操作数的说明,实际 的字节码中也是没有的,根据这个你能很清楚的得到实际上使用的是哪个成员或者调用的是哪个方法,这也是javap为我们提供的便利。
说完上面这些你现在应该很容易看懂上面的结果和下面将要叙述的内容了。其它更进一步的有关java字节码的信息请自己查找资料。
先看看最开始的部分,很像一个标准的c++类的声明,确实如此。成员声明的后面没有了成员初始化赋值语句和初始化块,那么这些语句何时执行的呢?先不要急,继续往下看。
第二块,是一个Method static {},对比看看第一部分,它被处理为一个静态的方法(从前面的Method可以看出),这就是源代码中的静态初始化块,从后面的语句可以看出它执行的就是 System.out.println("Child's static initialize block")语句,由于这个方法是没有方法名的,所以它不能被显式的调用,它在何处调用后面会有叙述。
第三块,缺省构造方法的实现,这是本文的重点,下面详细讲解。由于源代码中的缺省构造方法没有显式调用this 方法,因此没有this调用(对比看看下一块的有参的构造方法的前两句),同时也没有显式的super调用,那么隐式调用父类的缺省构造方法,也就是前两 条语句(主要是语句invokespecial #14 <Method Parent()>),它调用父类的构造方法,和这个类的构造相似(你可以使用javap ?c ?classpath . Parent反编译父类的字节码看看这个类的构造过程);紧接着的是执行源代码中的第一条初始化赋值语句cm2=10(即接下来的三条语句,主要是 bipush 10和putfield #15 <Field int cm2>,此处回答了第一块中的疑问,即初始化赋值语句到哪儿去了。);接下来是执行cm3=cmethod()(接下来的四条语句);然后是执行 初始化块中的内容System.out.println("Child's instance initialize block")(接下来的三条语句);java规范内部约定的内容至此执行完毕,开始执行构造方法的方法体中的内容,即co=new Other()(接下来的五条语句)和System.out.println("Child's default constructor")(接下来的三条语句),最后方法执行完毕返回(最后一条语句return)。
剩下的几块相信应该不用解释了吧,有参构造方法调用无参构造方法然后执行自己的方法体,成员方法cmethod执行一条打印语句然后返回一个常量3,静态方法staticmethod执行一条打印语句。
另外需要说明一下的是你可以将有参构造方法中的this调用去掉,然后看看反编译的结果,你会发现两个构造方法 非常的类似,如果你将两个构造方法的内容改为一样的,那么反编译后的生成也将是同样的。从这个可以说明本文开始的构造顺序的说明中构造方法中this调用 的判断是在编译阶段就完成的,而不是在运行阶段(说明中的意思好像是这个判断是在运行时进行的)。
对构造过程的另一个细节你可能还不相信,就是顺序中的第二条关于非静态成员的赋予缺省初始值(内存分配部分无法 考证,这是java虚拟机自动完成的),这个你可以通过在子类Child的cmethod方法的最开始用 System.out.println(cm3)打印cm3的值(输出为0,其它类型成员的值可以通过类似的方法得到)。
下面来讲解另一个还没有解决的问题:静态成员初始化和静态初始化块的执行是在何时完成的?这个可以通过一个小小 的试验推断得到:是在第一次使用该类对象时进行的(注意是类对象而不是类实例,对于类的公有静态成员可以直接通过类名进行访问,并不需要生成一个类实例, 这就是一次类对象的使用而非类实例的使用,如果在生成第一个类实例前没有使用过该类对象,那么在构造第一个类实例前先完成类对象的构造(即完成静态成员初 始化以及执行静态初始化块),然后再执行以上类实例的构造过程),试验的步骤如下:
1.修改main方法,将其中的System.out.println(Child.scm1)和c= new Child(10)都注释掉(不要删除,后面还需要用到这两个语句),编译运行程序,输出将只有program start和program end,这说明没有使用类对象也没有生成类实例时不进行静态成员的构造。
2.将System.out.println(Child.scm1)的注释取消,编译运行后输出多了父类和子类的静态初始化块部分的执行输出(使用子类的类对象将导致生成父类的类对象,父类先于子类构造)。
3.将System.out.println(Child.scm1)注释掉并取消c= new Child(10)的注释,编译运行后输出只比最开始没有注释任何语句时少了一条(输出Child.scm1的值10)
从以上的试验中我们可以得到前面的结论。本文至此可以说结束了,由于本人的java功底并不很扎实,java规范看得也不完整,因此文中可能有错误,如果您觉得某些地方有错误的话,欢迎通过mail联系。
+++++++++++++++++++++++++++++Java构造函数运行解析 +++++++++++++++++++
源代码如下:
import java.util.*;
class super1
{
{
System.out.println("super1 ok");
}
super1()
{
System.out.println("3");
}
}
class Employee extends super1
{
private String name;
private double salary=1500.00;
private Date birthday;
public Employee(String n,Date DoB)
{
System.out.println("2");
name=n;
birthday=DoB;
}
public Employee(String n)
{
this(n,null);
System.out.println("4");
}
}
class Manager extends Employee
{
{
System.out.println("Manager ok");
}
private String department;
public Manager(String n,String d)
{
super(n);
department=d;
}
}
public class Test1
{
public static void main(String args[])
{
new Manager("Smith","sales");
}
}
程序运行结果:
super1 ok
3
2
4
Manager ok
生成成功(总时间:1 秒)
new Manager("Smith","sales")调用过程:
(1)绑定构造函数参数。其实就是传递参数的过程
(2)查看是否有this()语句。没有。虽然没有使用this()语句调用构造函数,但是该步骤不能省略
(3)调用super()语句,此时,程序跳转到Public Employee(String n)。
(4)绑定构造函数参数String n
(5)查看是否有this()。有,则执行构造函数public Employee(String n,Date DoB)
(6)绑定构造函数参数String n,Date DoB
(7)查看是否有this()语句。没有
(8)执行有系统自动插入的super()语句:执行super1()
(9)执行显式初始化语句System.out.println("super1 ok");
(10)执行构造函数语句System.out.println("3");
(11)执行显式初始化语句private double salary=1500.00;
(12)执行构造函数语句System.out.println("2");同时执行name=n;birthday=DoB;
(13)执行构造函数语句System.out.println("4");
(14)执行显式初始化语句System.out.println("Manager ok");
(15)执行构造函数语句department=d;
几点总结:
(1)对象是由new运算符创建的,且在任何构造函数执行之前就已经创建完毕了
(2)构造函数的执行总是“向上”的:而且总是先执行完父类的构造函数
(3)在构造函数中,没有this()语句则由super()语句。没有this()时,或者自己编写super(),或者由系统自动调
用 super()
(4)显式初始化语句总是先于构造函数语句,但后于super()或this()
+++++++++++++++++++java 构造函数的执行过程++++++++++++++++++++++++
类初始化时构造函数调用顺序:
(1)初始化对象的存储空间为零或null值;
(2)调用父类构造函数;
(3)按顺序分别调用类成员变量和实例成员变量的初始化表达式;
(4)调用本身构造函数。
例子:
public class Dollar extends Money{
Rmb r=new Rmb();
public Dollar(){
System.out.println("Dollar is construct!");
}
public static void main(String[] args){
new Dollar();
}
}
class Money{
public Money(){
System.out.println("Money is construct");
}
}
class Rmb{
public Rmb(){
System.out.println("RMB is construct");
}
}
输出结果:
Money is construct
RMB is construct
Dollar is construct!
++++++深度理解JAVA本身的构造器及子父类构造方法的初始化顺序++++++++
我们说构造器是一种方法,就象讲澳大利亚的鸭嘴兽是一种哺育动物。(按:老外喜欢打比喻,我也就照着翻译)。要理解鸭嘴兽,那么先必须理解它和其他哺育动 物的区别。同样地,要理解构造器,那么就要了解构造器和方法的区别。所有学习java的人,尤其是对那些要认证考试的,理解构造器是非常重要的。下面将简 单介绍一下 ,最后用一个表作了些简单的总结。
功能和作用的不同
构造器是为了创建一个类的实例。这个过程也可以在创建一个对象的时候用到:Platypus p1 = new Platypus();
相反,方法的作用是为了执行java代码。
修饰符,返回值和命名的不同
构造器和方法在下面三个方便的区别:修饰符,返回值,命名。和方法一样,构造器可以有任何访问的修饰: public, protected, private或者没有修饰(通常被package 和 friendly调用). 不同于方法的是,构造器不能有以下非访问性质的修饰: abstract, final, native, static, 或者 synchronized。
返回类型也是非常重要的。方法能返回任何类型的值或者无返回值(void),构造器没有返回值,也不需要void。
最后,谈谈两者的命名。构造器使用和类相同的名字,而方法则不同。按照习惯,方法通常用小写字母开始,而构造器通常用大写字母开始。构造器通常是一个名词,因为它和类名相同;而方法通常更接近动词,因为它说明一个操作。
"this"的用法
构造器和方法使用关键字this有很大的区别。方法引用this指向正在执行方法的类的实例。静态方法不能使用this关键字,因为静态方法不属于类的实 例,所以this也就没有什么东西去指向。构造器的this指向同一个类中,不同参数列表的另外一个构造器,我们看看下面的代码:
|
在上面的代码中,有2个不同参数列表的构造器。第一个构造器,给类的成员name赋值,第二个构造器,调用第一个构造器,给成员变量name一个初始值 "John/Mary Doe".
在构造器中,如果要使用关键字this,那么,必须放在第一行,如果不这样,将导致一个编译错误。
"super"的用法
构造器和方法,都用关键字super指向超类,但是用的方法不一样。方法用这个关键字去执行被重载的超类中的方法。看下面的例子:
|
在上面的例子中,使用super.getBirthInfo()去调用超类Mammal中被重载的方法。
构造器使用super去调用超类中的构造器。而且这行代码必须放在第一行,否则编译将出错。看下面的例子:
|
在上面这个没有什么实际意义的例子中,构造器 Child()包含了 super,它的作用就是将超类中的构造器SuperClassDemo实例化,并加到 Child类中。
编译器自动加入代码
编译器自动加入代码到构造器,对于这个,java程序员新手可能比较混淆。当我们写一个没有构造器的类,编译的时候,编译器会自动加上一个不带参数的构造器,例如:public class Example {}
编译后将如下代码:
|
在构造器的第一行,没有使用super,那么编译器也会自动加上,例如:
|
编译器会加上代码,如下:
public class TestConstructors { |
仔细想一下,就知道下面的代码
|
经过会被编译器加代码形如:
|
继承
构造器是不能被继承的。子类可以继承超类的任何方法。看看下面的代码:
|
类 SubClass 自动继承了父类中的sayHi方法,但是,父类中的构造器 Example()却不能被继承。
以下在构造器 里构造器自己是不对的public test() {
s = new test();
}
异常:
xception in thread "main" java.lang.StackOverflowError
at father.test.<init>(test.java:15)
at father.test.<init>(test.java:15)
at father.test.<init>(test.java:15)
at father.test.<init>(test.java:15)
........
因为不断的构造自己了.死循环了.
还要学会修饰符的应用后,本类的范围.
构造方法的初始化顺序 |
想像一下你正在用java写程序,并且用下面的代码初始化类 A 和 B 的对象: class A { int a = f(); int f() { return 1; } } class B extends A { int b = a; int f() { return 2; } } public class CtorDemo1 { public static void main(String args[]) { B bobj = new B(); System.out.println(bobj.b); } }
现在,好像很明显的当初始化完成后,bobj.b的值将是1。毕竟,类B中的b 的值是用类A中的a的值初始化的,而a 是用f 的值初始化的,而它的值为1,对吗? 实际上, bobj.b 的值是2,要知道为什么需要知道对象初始化的问题。 当一个对象被创建时,初始化是以下面的顺序完成的: 1. 设置成员的值为缺省的初始值 (0, false, null) 2. 调用对象的构造方法 (但是还没有执行构造方法体) 3. 调用父类的构造方法 4. 使用初始化程序和初始块初始化成员 5. 执行构造方法体
看看在实际中是如何一步一步完成的,看看下面的例子: class A { A() { System.out.println("A.A called"); } }
class B extends A { int i = f(); int j;
{ j = 37; System.out.println("initialization block executed"); }
B() { System.out.println("B.B called"); }
int f() { System.out.println("B.f called"); return 47; } } public class CtorDemo2 { public static void main(String args[]) { B bobj = new B(); } } 程序的输出是: A.A called B.f called initialization block executed B.B called B 的构造方法被调用,但是最先做的事情是隐含的调用父类的构造方法。父类必须自己负责初始化它自己的状态而不是让子类来做。 然后B对象的成员被初始化,这包含一个对B.f 的调用和包围在{}中的初始块的执行。最后B的构造方法体被执行。 你可能会问“什么是对父类的构造方法的隐含调用”。这意味着如果你的构造方法的第一行不是下面内容之一: super(); super(args); this(); this(args); 则有下面的调用: super(); 提供给构造方法的第一行。 如果类没有构造方法呢?在这种情况下,一个缺省的构造方法(也叫"无参构造方法")由java编译器自动生成。缺省构造方法只有在类没有任何其它的构造方法时才产生。 更深入的明白这个,假设在文件A.java中有这样的代码:
public class A { public static void main(String args[]) { A aref = new A(); } } 如果你想编译然后列出A.class 中的字节码,输入下面的内容: $ javac A.java $ javap -c -classpath . A 输出: Compiled from A.java public class A extends java.lang.Object { public A(); public static void main(java.lang.String[]); } Method A() 0 aload_0 1 invokespecial #1 4 return Method void main(java.lang.String[]) 0 new #2 3 dup 4 invokespecial #3 7 astore_1 8 return 在main 中,注意对 A 的构造方法的调用(就是invokespecial 行),以及A的构造方法中产生的类似的对Object 构造方法的调用。 如果父类没有缺省构造方法,你必须明确使用"super(args)"调用父类的某个构造方法,例如,下面是一个错误的用法: class A { A(int i) {} } class B extends A {} 在上面的情况下, A 没有缺省的构造方法,但是B的构造方法必须调用A的某个构造方法。 让我们来看看初始化的另一个例子: class A { A() { System.out.println("A.A called"); } A(int i) { this(); System.out.println("A.A(int) called"); } } class B extends A { int i = f(); int j; { j = 37; System.out.println("initialization block executed"); } B() { this(10); System.out.println("B.B() called"); } B(int i) { super(i); System.out.println("B.B(int) called"); } int f() { System.out.println("B.f called"); return 47; } } public class CtorDemo3 { public static void main(String args[]) { B bobj = new B(); } } 程序的输出是: A.A called A.A(int) called B.f called initialization block executed B.B(int) called B.B() called 这 个例子明确使用super() 和 this() 调用。this()调用是调用同一个类中的另一个构造方法;这个方法被称为“显式构造方法调用”。当那样的构造方法被调用,它将执行通常的super() 过程以及后续的操作。这意味着A.A 的方法体在A.A(int)之前执行,而这两个都在B.B(int) 和B.B 前执行。 如果返回第一个例子,你就可以回答为什么打印的是2而不是1。B 没有构造方法,因此生成一个缺省构造方法,然后它调用super(),然后调用A 产生的缺省构造方法。 然后A中的成员被初始化,成员a 被设置为方法f()的值,但是因为B 对象正被初始化,f() 返回值2。换句话说,调用的是B中的f()方法。 A产生的构造方法体被执行,然后B的成员被初始化,而b 被赋予值a,也就是2。最后,B的构造方法被执行。 最后一个例子说明了第一个例子的一个小小的变异版本: class A { int a = f(); int f() { return 1; } } class B extends A { int b = 37; int f() { return b; } } public class CtorDemo4 { public static void main(String args[]) { B bobj = new B(); System.out.println(bobj.a); System.out.println(bobj.f()); } } 程序的输出是: 0 37 你可能会期望输出的两个值bobj.a 和bobj.f()是一样的,但是正如你看到的他们不一样。这是正确的,即使是在a是从B的f方法中初始化的并且打印的是a 和 B的 f 方法的值。 这儿的问题是当a通过对B的f方法调用而初始化,而该方法返回成员b的值,而该成员还没有被初始化。因为这个,b的值就是刚开始的初始值0。 这些例子解释了编程中重要的一点――在对象的构造阶段调用可重载的方法是不明智的。 |
父类的成员变量的初始化值--〉如果初始化成员变量时要调用父类的方法(如private int a=getData();),就会执行此父类的方法. 但是如果此方法被子类覆盖,那么这里是调用子类的方法(getData())。-->执行父类的构造函数-->子类的成员变量的初始化值- -〉执行子类的构造函数。 技巧:其实继承就是可以把子父类按规定的顺序组合起来。 先是父类的属性在前,在是子类的属性,再是父类的构造方法,再是子类的构造方法,再是父类与子类的方法,如果方法有覆盖的,用子类的。---注意顺序。 错错.........错了...
2008-01-23 修正:
初始化 顺序应该是.
父静态变量-->子静态变量-->父非静态变量-->父静态代码块-->父构造函数------>子非变量-->子静态代码块-->子构造函数