类的初始化顺序

钱太陈

贡献于2016-12-10

字数:4713 关键词: Java开发

关键字: java 面试题 初始化 大家在去参加面试的时候,经常会遇到这样的考题:给你两个类的代码,它们之间是继承的关系,每个类里只有构造器方法和一些变量,构造器里可能还有一段代码对变量值进行了某种运算,另外还有一些将变量值输出到控制台的代码,然后让我们判断输出的结果。这实际上是在考查我们对于继承情况下类的初始化顺序的了解。 我们大家都知道,对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是(静态变量、静态初始化块)>(变量、初始化块)>构造器。我们也可以通过下面的测试代码来验证这一点: 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. }   public class InitialOrderTest { // 静态变量 public static String staticField = "静态变量"; // 变量 public String field = "变量"; // 静态初始化块 static { System.out.println(staticField); System.out.println("静态初始化块"); } // 初始化块 { System.out.println(field); System.out.println("初始化块"); } // 构造器 public InitialOrderTest() { System.out.println("构造器"); } public static void main(String[] args) { new InitialOrderTest(); } } 运行以上代码,我们会得到如下的输出结果: 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. }   class Parent { // 静态变量 public static String p_StaticField = "父类--静态变量"; // 变量 public String p_Field = "父类--变量"; // 静态初始化块 static { System.out.println(p_StaticField); System.out.println("父类--静态初始化块"); } // 初始化块 { System.out.println(p_Field); System.out.println("父类--初始化块"); } // 构造器 public Parent() { System.out.println("父类--构造器"); } } public class SubClass extends Parent { // 静态变量 public static String s_StaticField = "子类--静态变量"; // 变量 public String s_Field = "子类--变量"; // 静态初始化块 static { System.out.println(s_StaticField); System.out.println("子类--静态初始化块"); } // 初始化块 { System.out.println(s_Field); System.out.println("子类--初始化块"); } // 构造器 public SubClass() { System.out.println("子类--构造器"); } // 程序入口 public static void main(String[] args) { new SubClass(); } } 运行一下上面的代码,结果马上呈现在我们的眼前: 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. }   public class TestOrder { // 静态变量 public static TestA a = new TestA(); // 静态初始化块 static { System.out.println("静态初始化块"); } // 静态变量 public static TestB b = new TestB(); public static void main(String[] args) { new TestOrder(); } } class TestA { public TestA() { System.out.println("Test--A"); } } class TestB { public TestB() { System.out.println("Test--B"); } } 运行上面的代码,会得到如下的结果: 1. Test--A 2. 静态初始化块 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 ClassNotFoundException public static Class forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException 第二个方法值得注意的就是第二个参数boolean initialize,如果我们把这个参数设置为false,那么当我们加载完类后就不会执行静态代码和静态的初始化动作。只有当我们new一个对象的时候才会初始化。而第三个参数是用来指明类的加载器的。 如果查看java.lang.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一个对象和调用类的静态方法),静态代码是在类加载后立刻执行,而对于显示加载(第一种是用java.lang.Class的forName(String str)方法,第二种是用java.lang.ClassLoader的loadClass())就如同我上面所说,加载过程是可以由我们来控制的。 2 实例化代码执行是载构造函数执行之前,涉及到继承时,父类的构造函数执行之前执行父类里的实例化代码,子类的构造函数执行之前执行子类的实例化代码。所以这样可以保证子类中用到的变量都是已经经过父类初始化的,从而保证了初始化的正确性。 呵呵,这些是我学习J2SE的时候总结的,今天和大家分享。 臧圩人 2008-07-06 回复Unmi: 你的建议很好 学习任何东西都有一个由浅入深的过程,能够掌握的广泛和深入固然好,不过有时候够用也是一个不错的标准 Unmi 2008-07-06 实际上你理解了内存中的对象模型就用不着这样的长篇累椟,这也是java人相比与c++人员的一种缺陷,逃避了对内存,指针的感性理解,有些问题,譬如,转型,以及如上的类的初始化顺序反而把自己搞糊涂了。 spiritfrog 2008-07-05 引用 那么对于静态变量和静态初始化块之间、变量和初始化块之间的先后顺序又是怎样呢?是否静态变量总是先于静态初始化块,变量总是先于初始化块就被初始化了呢?实际上这取决于它们在类中出现的先后顺序 对于这个,应该不算是规律, 它们的先后顺序,是由你变量的初始化顺序决定的。 倒是lz没有提出为什么静态变量会先初始化。 spiritfrog 2008-07-05 感觉lz这个系列对面试帮助确实很大, 也加深了基础的理解。 支持! sooo 2008-07-04 支持这样的文章 weidewei 2008-07-01 给大家最简单的记忆方法: 先静态后动态 先定义后构造函数 四个顺序,很好记吧! yinleiyoung 2008-07-01 言简意赅,让我茅塞顿开。 为什么有人说错了? dmewy 2008-07-01 你真是大错特错... 完全错误.. yang_rabbit 2008-07-01 学习了,谢谢 naff 2008-07-01 good!!!! andy54321 2008-06-30 好的, 清晰明了, 明白了许多 zzg2008 2008-06-30 讲解得很清晰很透彻,学习了。 huyuhong001 2008-06-30 可以不错 jylovejava 2008-06-30 不错!学习了 InnocentBoy 2008-06-30 有噱头,不实用。个人感觉。 jike0616 2008-06-30 哇!又学习了。。。感谢LZ lovinchan 2008-06-29 总结得很好,重新认识了很多。。 -JAVA程序员JAVA工程师面试必看

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ]
0 人已下载

下载文档

相关文档