JAVA面试题解惑系列(一)——类的初始化顺序-JAVA程序员JAVA工程师面试必看
- 格式:doc
- 大小:91.50 KB
- 文档页数:6
java类的初始化顺序
类在使⽤时,会加载进内存⾥,然后进⾏成员属性等初始化。
顺序:
1、在加载完后,所有成员属性都会有⼀个默认值,会在存储空间初始化为⼆进制的0。
2、按照定义顺序,初始化⽗类静态属性和静态代码块,如果⽗类还有⽗类,⼀直逆向初始化⽗类,直⾄根⽗类
3、按照定义顺序,初始化⼦类静态属性和静态代码块
4、按照定义顺序,初始化⽗类普通属性和普通代码块
5、执⾏⽗类构造器
6、按照定义顺序,初始化⼦类普通属性和普通代码块
7、执⾏⼦类构造器
总体⽽⾔,先⽗类,后⼦类;先静态属性,后普通属性;先属性,后构造器。
注意:
1、静态属性只初始化⼀次,在第⼀次使⽤时初始化后,⽆论创建多少次对象,都不会再初始化。
2、关于构造器:
1)如果在类中,没有定义构造器,编译器会⾃动创建⼀个⽆参构造器。
反之,如果定义了构造器,编译器就不会再添加⽆参构造器了。
2)如果⽗类只定义了有参构造器,⽽没有⽆参构造器,并且在⼦类中没有显⽰调⽤⽗类的有参构造器,则在初始化时会报错。
因在⼦类中,默认会调⽤⽗类的⽆参构造器,⽽⽗类没有⽆参构造器,就出错了。
java类变量初始化顺序假定有⼀个类定义如下:package com.zhang;public final class Girl {// static代码块1private static String sex = "female";// 成员⽅法代码块1private String name = "anonymous";// static代码块2static {System.out.println("static1");}// 成员⽅法代码块3public Girl() {System.out.println("constructor");}// static代码块3static {System.out.println("static2");}// 成员⽅法代码块2private int age = 18;}第⼀次加载类,并创建该类对象时,静态变量、成员变量的初始化顺序,静态代码的执⾏顺序是怎样的?创建⼀个类对象,如果该类没有初始化,则先初始化该类(执⾏ clinit ⽅法),然后执⾏构造函数(init ⽅法),具体就是先执⾏ class ⽂件中的 static 代码块,然后再执⾏构造函数代码块。
观察上⾯类的 class ⽂件 static 代码块:可以看出,static 代码块内容就是,把 static 语句按顺序搜集起来,static 变量赋值也是 static 语句,伪代码如下:static {sex = "female";System.out.println("static1");System.out.println("static2");}构造函数代码块:对应的伪代码如下:public Girl() {<init>name = "anonymous";age = 18;System.out.println("constructor");}⾸先执⾏ <init> ⽅法,即按顺序收集构造函数外部的语句,然后再执⾏构造函数内的语句。
对象初始化流程:我们根据一段代码来分析对象初始化流程:/***基类包含一静态变量、包含一实例变量*包含一个静态初始化块以及一个构造子*/class Base{public static int a = 10;public int b = 20;static{System.out.println("Static Init Base " + a);//System.out.println("Null Init " + b);}public Base(){System.out.println("Init Base " + this.b);}}/***一级子类和基类包含的内容一样**/class SuperClass extends Base{public static int a1 = getSuperStaticNumber();public int b1 = getSuperInstanceNumber();public SuperClass(){System.out.println("Init SuperClass" + this.b1); }static{System.out.println("Static Init SuperClass" + a1); }public static int getSuperStaticNumber(){System.out.println("Static member init");return 100;}public int getSuperInstanceNumber(){System.out.println("Instance member init");return 200;}}/***二级子类为测试该代码的驱动类*/public class SubClass extends SuperClass{public static int a2 = getStaticNumber();public int b2 = getInstanceNumber();public SubClass(){System.out.println("Init SubClass " + this.b2);}public static int getStaticNumber(){System.out.println("Static member init Sub");return 1000;}public int getInstanceNumber(){System.out.println("Instance member init Sub");return 2000;}public static void main(String args[]){new SubClass();}static{System.out.println("Static Init " + a2);}}这段代码会有以下输出:Static Init Base 10Static member initStatic Init SuperClass 100Static member init SubStatic Init 1000Init Base 20Instance member initInit SuperClass 200Instance member init SubInit SubClass 2000[1]对象在初始化过程,JVM会先去搜索该类的顶级父类,直到搜索到我们所定义的SubClass继承树上直接继承于Object类的子类,在这里就是Base 类;[2]然后JVM会先加载Base类,然后初始化Base类的静态变量a,然后执行Base类的静态初始化块,按照这样第一句话会输出:Static Init Base 10【*:此时该类还未调用构造函数,构造函数是实例化的时候调用的】[3]然后JVM按照继承树往下搜索,继续加载Base类的子类,按照静态成员函数->静态成员变量->静态初始化块的顺序往下递归,直到加载完我们使用的对象所在的类。
Java初始化顺序/thread.shtml?topicId=36138&forumId=19初始化(initialization)其实包含两部分:1.类的初始化(initialization class & interface)2.对象的创建(creation of new class instances)。
因为类的初始化其实是类加载(loading of classes)的最后一步,所以很多书中把它归结为“对象的创建”的第一步。
其实只是看问题的角度不同而已。
为了更清楚的理解,这里还是分开来。
顺序:类的加载肯定是第一步的,所以类的初始化在前。
大体的初始化顺序是:类初始化-> 子类构造函数-> 父类构造函数-> 实例化成员变量-> 继续执行子类构造函数的语句下面结合例子,具体解释一下。
1。
类的初始化(Initialization classes and interfaces),其实很简单,具体来说有:(a)初始化类(initialization of class),是指初始化static field 和执行static初始化块。
例如:class Super {static String s = “initialization static field”; //初始化static field,其中“= “initialization static field” ”又叫做static field initializer// static初始化块,又叫做static initializer,或static initialization blockstatic{System.out.println(“This is static initializer”);}}btw,有些书上提到static initializer和static field initializer的概念,与之对应的还有instance initializer和instance variable initializer。
java类的加载顺序(⾯试题)
初始化:
1. 静态属性:static 开头定义的属性
2. 静态⽅法块: static {} 圈起来的⽅法块
3. 普通属性:未带static定义的属性
4. 普通⽅法块: {} 圈起来的⽅法块
5. 构造函数:类名相同的⽅法
6. ⽅法:普通⽅法
实例化:按照上⾯的顺序,但是不重新加载静态修饰的属性以及⽅法了,因为第⼀次初始化的时候,已经被加载过了,可以直接调⽤。
直接运⾏2,3,4,5,6
普通类:
静态变量
静态代码块
普通变量
普通代码块
构造函数
继承的⼦类:
⽗类静态变量
⽗类静态代码块
⼦类静态变量
⼦类静态代码块
⽗类普通变量
⽗类普通代码块
⽗类构造函数
⼦类普通变量
⼦类普通代码块
⼦类构造函数
抽象的实现⼦类: 接⼝ - 抽线类 - 实现类
接⼝静态变量
抽象类静态变量
抽象类静态代码块
实现类静态变量
实习类静态代码块
抽象类普通变量
抽象类普通代码块
抽象类构造函数
实现类普通变量
实现类普通代码块
实现类构造函数
接⼝注意:
声明的变量都是静态变量并且是final的,所以⼦类⽆法修改,并且是固定值不会因为实例⽽变化
接⼝中能有静态⽅法,不能有普通⽅法,普通⽅法需要⽤defalut添加默认实现
接⼝中的变量必须实例化
接⼝中没有静态代码块、普通变量、普通代码块、构造函数。
java变量初始化顺序第⼀次实例化⼀个类时,初始化优先顺序为:1、⽗类中的静态成员变量和静态代码块初始化2、本类中的静态成员变量和静态代码块初始化3、⽗类中的实例成员初始化4、⽗类中的构造⽅法5、本类中的实例成员初始化6、本类中的构造⽅法成员变量和⽰例变量初始化的区别: 成员变量:每次新建对象的时候都会初始化; 实例变量:只在第⼀次调⽤(包括实例化或通过类访问)的时候⼀次性初始化全部的静态变量 实例变量⽰例:class Tag {Tag(int marker) {System.out.println("Tag(" + marker + ")");}}class Card {Tag t1 = new Tag(1); // Before constructorCard() { // Indicate we're in the constructor:System.out.println("Card()");t3 = new Tag(33); // Re-initialize t3}Tag t2 = new Tag(2); // After constructorvoid f() {System.out.println("f()");}Tag t3 = new Tag(3); // At end}public class OrderOfInitialization {public static void main(String[] args) {Card t = new Card();t.f(); // Shows that construction is done}}运⾏结果: Tag(1) Tag(2) Tag(3) Card() Tag(33) f()静态变量⽰例:public class Bowl {Bowl(int marker) {System.out.println("Bowl(" + marker + ")");}void f(int marker) {System.out.println("f(" + marker + ")");}}public class Table {static Bowl b1 = new Bowl(1);Table() {System.out.println("Table()");b2.f(1);}void f2(int marker) {System.out.println("f2(" + marker + ")");}static Bowl b2 = new Bowl(2);}public class Cupboard {Bowl b3 = new Bowl(3);//注意实例变量与静态变量共存的时候,即使静态变量靠后,也先初始化static Bowl b4 = new Bowl(4);Cupboard() {System.out.println("Cupboard()");b4.f(2);}void f3(int marker) {System.out.println("f3(" + marker + ")");}static Bowl b5 = new Bowl(5);}public class Test {public static void main(String[] args) throws Exception { System.out.println("Creating new Cupboard() in main");new Cupboard();System.out.println("Creating new Cupboard() in main");new Cupboard();t2.f2(1);t3.f3(1);}static Table t2 = new Table();static Cupboard t3 = new Cupboard();}运⾏结果:Bowl(1)Bowl(2)Table()f(1)Bowl(4)Bowl(5)Bowl(3)Cupboard()f(2)Creating new Cupboard() in mainBowl(3)Cupboard()f(2)Creating new Cupboard() in mainBowl(3)Cupboard()f(2)f2(1)f3(1)。
Java初始化顺序(静态变量、静态初始化块、实例变量、实例初始化块、构造⽅法)1、执⾏顺序1.1、⼀个类中的初始化顺序类内容(静态变量、静态初始化块) => 实例内容(变量、初始化块、构造器)1.2、两个具有继承关系类的初始化顺序⽗类的(静态变量、静态初始化块)=> ⼦类的(静态变量、静态初始化块)=> ⽗类的(变量、初始化块、构造器)=> ⼦类的(变量、初始化块、构造器)⽰例如下:(结果见注释)1class A {2public A() {3 System.out.println("Constructor A.");4 }56 {7 System.out.println("Instance Block A.");8 }9static {10 System.out.println("Static Block A.");11 }1213public static void main(String[] args) {14new A();/*15 * Static Block A. Instance Block A. Constructor A.16*/17 }18 }1920class B extends A {21public B() {22 System.out.println("Constructor B.");23 }2425 {26 System.out.println("Instance Block B.");27 }28static {29 System.out.println("Static Block B.");30 }3132public static void main(String[] args) {33new A();/*34 * Static Block A. Static Block B. Instance Block A. Constructor A.35*/36 System.out.println();37new B();/*38 * Instance Block A. Constructor A. Instance Block B. Constructor B.39*/// 静态成员和静态初始化块只会执⾏⼀次。
Java中class的初始化顺序 由于Java 中的⼀切东西都是对象,所以许多活动变得更加简单,这个问题便是其中的⼀例。
除⾮真的需要代码,否则那个⽂件是不会载⼊的。
通常,我们可认为除⾮那个类的⼀个对象构造完毕,否则代码不会真的载⼊。
由于static ⽅法存在⼀些细微的歧义,所以也能认为“类代码在⾸次使⽤的时候载⼊”。
⾸次使⽤的地⽅也是static 初始化发⽣的地⽅。
装载的时候,所有static 对象和static 代码块都会按照本来的顺序初始化(亦即它们在类定义代码⾥写⼊的顺序)。
当然,static 数据只会初始化⼀次。
简要的说就是,在类有继承关系时,类加载器上溯造型,进⾏相关类的加载⼯作。
⽐如:Class B extends Class A当我们new B()时,类加载器⾃动加载A的代码class的初始化顺序通常是以下这样的初始化顺序:(static对象和static代码块,依据他们的顺序进⾏初始化)->成员变量和代码块(依据他们的顺序进⾏初始化)->构造函数例如:package cn.d;public class ClassInit {public static void main(String[] args) {new B();System.out.println("------------");new B();}}class A {static {System.out.println("A的static代码块...");// 1}{System.out.println("A的代码块...");// 1}public String s1 = prtString("A的成员变量...");public static String s2 = prtString("A的static变量...");// 2public A() {System.out.println("A的构造函数...");}public static String prtString(String str) {System.out.println(str);return null;}}class B extends A {public String ss1 = prtString("B的成员变量...");{System.out.println("B的代码块...");}public static String ss2 = prtString("B的static变量...");// 3.public B() {System.out.println("B的构造函数...");}private static A a = new A();// 4.static {System.out.println("B的static代码块...");}}结果:A的static代码块...A的static变量...B的static变量...A的代码块...A的成员变量...A的构造函数...B的static代码块...A的代码块...A的成员变量...A的构造函数...B的成员变量...B的代码块...B的构造函数...------------A的代码块...A的成员变量...A的构造函数...B的成员变量...B的代码块...B的构造函数...解释:1. ⾸先加载A的静态代码快和静态变量,由于A中静态代码块刈写在前⾯,因此先加载静态代码块后加载静态变量。
java中类成员初始化顺序java中初始化类成员⽅法包括:1.显⽰域初始化,⽐如public int a=1;public static int b=1;2.初始化块,分为普通初始化块,静态初始化块;3.构造函数。
初始化的时候,⾸先是静态类的初始化⽅式执⾏,然后才是普通初始⽅式执⾏,并且初始化块总是先于构造函数执⾏,显式域初始化与初始化块的执⾏顺序按照代码中出现的顺序执⾏。
显式静态域初始化先于静态初始化块public class Hello{public static int staticA=1;public static int staticB;static{System.out.println("Static Inital Block Start");staticB=2;PrintAB();System.out.println("Static Inital Block End\n");}public static void PrintAB(){System.out.println("staticA:"+staticA);System.out.println("staticB:"+staticB);}public Hello(){Hello.PrintAB();}public static void main(String[] args){Hello h=new Hello();}}输出:Static Inital Block StartstaticA:1staticB:2Static Inital Block EndstaticA:1staticB:2显式静态域初始化后于静态初始化块public class Hello{public static int staticB;static{System.out.println("Static Inital Block Start");staticB=2;PrintAB();System.out.println("Static Inital Block End\n");}public static int staticA=1;public static void PrintAB(){System.out.println("staticA:"+staticA);System.out.println("staticB:"+staticB);}public Hello(){Hello.PrintAB();}public static void main(String[] args){Hello h=new Hello();}}输出:Static Inital Block StartstaticA:0staticB:2Static Inital Block EndstaticA:1staticB:2显式静态域初始化与初始化块按照出现顺序执⾏,最后执⾏构造函数, 注意noStaticD在两个构造快中间初始化,第⼀个初始化块打印时noStaticD=0,第⼆个块中变为1输出:NoStatic Inital Block for noStaticC StartnoStaticC:1noStaticD:1noStaticE:1noStaticF:0NoStatic Inital Block for noStaticC EndConstructor StartnoStaticC:1noStaticD:1noStaticE:1noStaticF:1Constructor END。
-JAVA程序员JAVA工程师面试必看JAVA面试题解惑系列(一)——类的初始化顺序关键字: java 面试题初始化作者:臧圩人(zangweiren)网址:>>>转载请注明出处!<<<大家在去参加面试的时候,经常会遇到这样的考题:给你两个类的代码,它们之间是继承的关系,每个类里只有构造器方法和一些变量,构造器里可能还有一段代码对变量值进行了某种运算,另外还有一些将变量值输出到控制台的代码,然后让我们判断输出的结果。
这实际上是在考查我们对于继承情况下类的初始化顺序的了解。
我们大家都知道,对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是(静态变量、静态初始化块)>(变量、初始化块)>构造器。
我们也可以通过下面的测试代码来验证这一点:Java代码1.public class InitialOrderTest {2.3. // 静态变量4. public static String staticField = "静态变量";5. // 变量6. public String field = "变量";7.8. // 静态初始化块9. static {10. System.out.println(staticField);11. System.out.println("静态初始化块");12. }13.14. // 初始化块15. {16. System.out.println(field);17. System.out.println("初始化块");18. }19.20. // 构造器21. public InitialOrderTest() {22. System.out.println("构造器");23. }24.25. public static void main(String[] args) {26. new InitialOrderTest();27. }28.}运行以上代码,我们会得到如下的输出结果:1.静态变量2.静态初始化块3.变量4.初始化块5.构造器这与上文中说的完全符合。
那么对于继承情况下又会怎样呢?我们仍然以一段测试代码来获取最终结果:Java代码1.class Parent {2. // 静态变量3. public static String p_StaticField = "父类--静态变量";4. // 变量5. public String p_Field = "父类--变量";6.7. // 静态初始化块8. static {9. System.out.println(p_StaticField);10. System.out.println("父类--静态初始化块");11. }12.13. // 初始化块14. {15. System.out.println(p_Field);16. System.out.println("父类--初始化块");17. }18.19. // 构造器20. public Parent() {21. System.out.println("父类--构造器");22. }23.}24.25.public class SubClass extends Parent {26. // 静态变量27. public static String s_StaticField = "子类--静态变量";28. // 变量29. public String s_Field = "子类--变量";30. // 静态初始化块31. static {32. System.out.println(s_StaticField);33. System.out.println("子类--静态初始化块");34. }35. // 初始化块36. {37. System.out.println(s_Field);38. System.out.println("子类--初始化块");39. }40.41. // 构造器42. public SubClass() {43. System.out.println("子类--构造器");44. }45.46. // 程序入口47. public static void main(String[] args) {48. new SubClass();49. }50.}运行一下上面的代码,结果马上呈现在我们的眼前:1.父类--静态变量2.父类--静态初始化块3.子类--静态变量4.子类--静态初始化块5.父类--变量6.父类--初始化块7.父类--构造器8.子类--变量9.子类--初始化块10.子类--构造器现在,结果已经不言自明了。
大家可能会注意到一点,那就是,并不是父类完全初始化完毕后才进行子类的初始化,实际上子类的静态变量和静态初始化块的初始化是在父类的变量、初始化块和构造器初始化之前就完成了。
那么对于静态变量和静态初始化块之间、变量和初始化块之间的先后顺序又是怎样呢?是否静态变量总是先于静态初始化块,变量总是先于初始化块就被初始化了呢?实际上这取决于它们在类中出现的先后顺序。
我们以静态变量和静态初始化块为例来进行说明。
同样,我们还是写一个类来进行测试:Java代码1.public class TestOrder {2. // 静态变量3. public static TestA a = new TestA();4.5. // 静态初始化块6. static {7. System.out.println("静态初始化块");8. }9.10. // 静态变量11. public static TestB b = new TestB();12.13. public static void main(String[] args) {14. new TestOrder();15. }16.}17.18.class TestA {19. public TestA() {20. System.out.println("Test--A");21. }22.}23.24.class TestB {25. public TestB() {26. System.out.println("Test--B");27. }28.}运行上面的代码,会得到如下的结果:1.Test--A2.静态初始化块3.Test--B大家可以随意改变变量a、变量b以及静态初始化块的前后位置,就会发现输出结果随着它们在类中出现的前后顺序而改变,这就说明静态变量和静态初始化块是依照他们在类中的定义顺序进行初始化的。
同样,变量和初始化块也遵循这个规律。
了解了继承情况下类的初始化顺序之后,如何判断最终输出结果就迎刃而解了。
∙10:13∙浏览 (4106)∙评论 (41)∙分类: JAVA面试题解惑系列∙收藏∙相关推荐评论zm2693450 2008-07-14好贴,顶起来臧圩人 2008-07-08回复狂放不羁:总结的很好,赞一个狂放不羁 2008-07-08静态代码为什么先于非静态代码这是因为静态代码是在类加载完毕后执行的,而加载类的顺序是先父类后子类,所以静态代码的执行是先执行父类的,然后执行子类的。
对于非静态变量以及实例初始化块都是在构造函数里的代码执行前执行。
所以静态代码是在类加载后执行,而实例代码是在构造函数执行前执行。
但是当我们显示控制类加载的时候情况有点变化,显示加载可以有关两种方法:第一种:利用forName方法当我们查API文档就会发现forName方法有两种形式。
分别如下:public static Class<?> forName(String className)throws ClassNotFoundExceptionpublic static Class<?> forName(String name,boolean initialize,ClassLoader loader)throws ClassNotFoundException第二个方法值得注意的就是第二个参数boolean initialize,如果我们把这个参数设置为false,那么当我们加载完类后就不会执行静态代码和静态的初始化动作。
只有当我们new一个对象的时候才会初始化。
而第三个参数是用来指明类的加载器的。
如果查看ng.Class类的源代码,上述两种方法最终都会调用Class类中的私有的native方法forName0(),此方法的声明如下:private static native Class forName0(String name, boolean init , ClassLoader loader)throws ClassNotFoundException;所以当我们调用Class.forName(name )时,其实是在方法内部调用了:forName0(name, true, ClassLoader.getCallerClassLoader());当我们调用Class.forName(name, initialize, loader )的时候,实际上此方法内部调用了:forName0(name, initialize, loader);第二种:利用Class对象获取的ClassLoader装载。
此方法也是在实例化时才执行静态代码的执行。
综上所述可以总结如下:1 对于隐式的加载(new一个对象和调用类的静态方法),静态代码是在类加载后立刻执行,而对于显示加载(第一种是用ng.Class的forName(String str)方法,第二种是用ng.ClassLoader的loadClass())就如同我上面所说,加载过程是可以由我们来控制的。
2 实例化代码执行是载构造函数执行之前,涉及到继承时,父类的构造函数执行之前执行父类里的实例化代码,子类的构造函数执行之前执行子类的实例化代码。
所以这样可以保证子类中用到的变量都是已经经过父类初始化的,从而保证了初始化的正确性。
呵呵,这些是我学习J2SE的时候总结的,今天和大家分享。
臧圩人 2008-07-06回复Unmi:你的建议很好学习任何东西都有一个由浅入深的过程,能够掌握的广泛和深入固然好,不过有时候够用也是一个不错的标准Unmi 2008-07-06实际上你理解了内存中的对象模型就用不着这样的长篇累椟,这也是java人相比与c++人员的一种缺陷,逃避了对内存,指针的感性理解,有些问题,譬如,转型,以及如上的类的初始化顺序反而把自己搞糊涂了。