Java变量初始化讨论(转载)
类成员如果没有被初始化会自动获取一个初始值,也称为默认值,例:
public class InitialValues { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub DefaultValues values = new DefaultValues(); System.out.println("boolean: "+values.bl); System.out.println("char: "+values.c); System.out.println("int: "+values.i); System.out.println("short: "+values.s); System.out.println("long: "+values.l); System.out.println("float: "+values.f); System.out.println("double: "+values.d); System.out.println("byte: "+values.bt); System.out.println("String: "+values.str); System.out.println("Object: "+values.obj); System.out.println("Array: "+values.arr); } } class DefaultValues { boolean bl; char c; int i; short s; long l; float f; double d; byte bt; String str; Object obj; int[] arr; }
运行结果:
boolean: false char: int: 0 short: 0 long: 0 float: 0.0 double: 0.0 byte: 0 String: null Object: null Array: null
char 的默认值是Unicode编码为0000的字符,也写作'\u0000',后三个(String,Object,数组)都是引用类型,默认值为null。
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧在任何方法(包括构造器)被调用之前得到初始化。例:
public class InitialValues { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Label label = new Label(); } } class Tag { Tag(int id){ System.out.println("Tag "+id); } } class Label { Tag tag1 = new Tag(1); public Label() { System.out.println("Label()"); tag3 = new Tag(33); } Tag tag2 = new Tag(2); public void print(){ System.out.println("print()"); } Tag tag3 = new Tag(3); }
运行结果:
Tag 1 Tag 2 Tag 3 Label() Tag 33
静态初始化只有在必要时刻才会进行。如果不创建Table 对象也不引用Table.b1或Table.b2那么静态的Bowl b1 和b2 永远都不会被创建。静态对象只有在被创建(或者第一次访问静态数据)的时候,它们才会被初始化。此后,静态对象不会再次被初始化。初始化的顺序是先“静态”(如果它们尚未因前面的对象创建过程而被初始化)后“非静态”
public class InitialValues { static Bowl bowl = new Bowl(5); static Table table = new Table(); static Cupboard cupboard = new Cupboard(); /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub new Cupboard(); new Cupboard(); bowl.print(5); table.print(5); cupboard.print(5); } } class Bowl { Bowl(int id) { System.out.println("Bowl " + id); } public void print(int id) { System.out.println("print Bowl " + id); } } class Table { static Bowl bowl1 = new Bowl(1); public Table() { System.out.println("Table()"); bowl2.print(2); } public void print(int id) { System.out.println("print Table " + id); } static Bowl bowl2 = new Bowl(2); } class Cupboard { Bowl bowl3 = new Bowl(3); static Bowl bowl4 = new Bowl(4); public Cupboard() { System.out.println("Cupboard()"); bowl4.print(4); } public void print(int id) { System.out.println("print Cupboard " + id); } }
运行结果(及对应的主函数中的语句):
Bowl 5 static Bowl bowl = new Bowl(5);
Bowl 1 Bowl 2 Table() print Bowl 2 static Table table = new Table(); Bowl 4 Bowl 3 Cupboard() print Bowl 4 static Cupboard cupboard = new Cupboard(); Bowl 3 Cupboard() print Bowl 4 new Cupboard(); Bowl 3 Cupboard() print Bowl 4 new Cupboard(); print Bowl 5 bowl.print(5); print Table 5 table.print(5); print Cupboard 5 cupboard.print(5);
Java 允许你将多个静态初始化动作组织成一个特殊的“静态子句”(有时也叫作“静态块”)。与其他静态初始化动作一样(这段代码仅执行一次)当你首次生成这个类的一个对象时(或者首次访问属于那个类的一个静态成员时)即便从未生成过那个类的对象。而非静态的代码块,只要实例化一个类,该类中的非静态代码块就会被执行一次。
涉及继承关系的初始化
public class InheritanceInitial { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub new Cartoon(); } } class Art { public Art() { System.out.println("Art constructor"); } } class Drawing extends Art { public Drawing() { System.out.println("Drawing constructor"); } } class Cartoon extends Drawing { public Cartoon() { System.out.println("Cartoon constructor"); } }
运行结果:
Art constructor
Drawing constructor
Cartoon constructor
可见构建过程是从基类“向外”扩展的,另外关于调用父类无参构造函数的其他讨论:
Java的继承中子类先调用父类无参构造函数是为了干什么,默认这个无参的构造函数什么也不做,那调用它干什么?
由于父类的参数定义域内一般不会对参数赋初值,当创建没有参数的父类对象时,有必要对其内部的参数进行初始化,所以父类的无参构造函数内部可能对一些参数进行了初始化工作。子类的构造器中提供的参数可能只是所有参数的一部分的值,若不先调 用父类的无参构造函数,则很可能有些重要的参数没有进行初始化赋值,在子类的其他方法进行操作时会出现严重的错误。所以有必要调用一次父类的无参构造函数。
Java中子类自动继承父类的无参构造方法吗?
首先子类中的方法必须调用父类的构造方法,如果父类中只有一个有参的构造方法,你在子类的构造方法必须调用,而且是子类的构造方法第一句话必须是调用父类的有参构造方法,否则编译器会提示错误的。如果父类中有无参的构造方法,你没有写的话会自动调用的。
如果类没有缺省的参数,或者想调用一个带参数的基类构造器,就必须用关键字super显示地调用基类构造器的语句,并且配以适当的参数列表,例:
public class InheritanceInitial { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub new Cartoon(1); } } class Art { public Art(int id) { System.out.println("Art constructor "+id); } } class Drawing extends Art { public Drawing(int id) { super(id); System.out.println("Drawing constructor "+id); } } class Cartoon extends Drawing { public Cartoon(int id) { super(id); System.out.println("Cartoon constructor "+id); } }
运行结果:
Art constructor 1 Drawing constructor 1 Cartoon constructor 1
构造器与多态
基类的构造器总是在导出类的构造过程中被调用的而且按照继承层次逐渐向上链接以使每个基类的构造器都能得到调用。这样做是有意义的因为构造器具有一项特殊的任务检查对象是否被正确地构造。导出类只能访问它自己的成员不能访问基类中的成员基类成员通常是private类型。只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此,必须令所有构造器都得到调用,否则就不可能正确构造完整对象。这正是编译器为什么要强制每个导出类部分都必须调用构造器的原因。
public class PolyConstructor { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub new RoundGlyph(5); } } abstract class Glyph { abstract void draw(); public Glyph() { System.out.println("Glyph() before draw()"); draw(); System.out.println("Glyph() after draw()"); } } class RoundGlyph extends Glyph { private int radius = 1; public RoundGlyph(int r) { radius = r; System.out.println("RoundGlyph.RoundGlyph() radius = "+radius); } @Override void draw() { // TODO Auto-generated method stub System.out.println("RoundGlyph.draw() radius = "+radius); } }
运行结果:
Glyph() before draw() RoundGlyph.draw() radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph() radius = 5
在Glyph中draw()方法是抽象的,这样设计是为了覆盖该方法。我们确实在RoungGlyph中强制覆盖了draw()。但是Glyph构造器会调用这个方法,结果导致了对RoundGlyph.draw()的调用,这看起来似乎是我们的目的。但是如果看到输出结果,我们会发现当Glyph的构造器调用draw()方法时,radius不是默认初始值1,而是0。
解决这个问题的关键是初始化的实际过程:
1在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制零。
2如前所述那样调用基类构造器。此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用)由于步骤1的缘故,我们此时会发现radius的值为0。
3按照声明的顺序调用成员的初始化方法。
4调用导出类的构造器主体。
初始化及类的加载
public class Beetle extends Insect { private int l = print("Insect.k is initialized"); public Beetle() { System.out.println("j="+j); System.out.println("k="+k); System.out.println("l="+l); } private static int x2 = print("static Beetle.x2 is initialized"); /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub new Beetle(); } Tag tag2 = new Tag(2); } class Tag { public Tag(int id) { System.out.println("Tag "+id); } } class Insect { private int i =9; protected int j, k; public Insect() { System.out.println("i="+i+" j="+j); j = 39; } private static int x1 = print("static Insect.x1 is initialized"); static int print(String s) { System.out.println(s); return 47; } Tag tag1 = new Tag(1); }
运行结果:
static Insect.x1 is initialized static Beetle.x2 is initialized Tag 1 i=9 j=0 Insect.k is initialized Tag 2 j=39 k=0 l=47
你在Beetle 上运行Java 时,所发生的第一件事情就是你试图访问Beetle.main()(一个static 方法),于是加载器开始启动并找出 Beetle 类被编译的程序代码,它被编译到了一个名为Beetle .class 的文件之中。在对它进行加载的过程中,编译器注意到它有一个基类(这是由关键字 extends 告知的),于是它继续进行加载。不管你是否打算产生一个该基类的对象,这都要发生。 如果该基类还有其自身的基类,那么第二个基类就会被加载,如此类推。接下来,根基类中的静态初始化,在此例中为Insect,即会被执行,然后是下一个导出类,以此类推。这种方式很重要,因为导出类的静态初始化可能会依赖于基类成员能否被正确初始化的。 至此为止,必要的类都已加载完毕(静态变量和静态块),对象就可以被创建了。 首先,对象中所有的原始类型都会被设为缺省值,对象引用被设为null——这是通过将对象内存设为二进制零值而一举生成的。然后,基类的构造器会被调用。在本例中,它是被自动调用的。但你也可以用super来指定对基类构造器的调用。基类构造器和导出类的构造器一样,以相同的顺序来经历相同的过程,即向上寻找基类构造器。在基类构造器完成之后(即根部构造器找到之后),实例变量(instance variables )按其次序被初始化,注意观察代码中的Tag(),最后构造器的其余部分被执行。
主要参考资料:http://wenku.baidu.com/view/d881bad3360cba1aa811daf2.html