[置顶] Java 类的实例变量初始化的过程 静态块、非静态块、构造函数的加载顺序

时间:2020-12-10 19:32:53
先看一道Java面试题:

 

[置顶]        Java 类的实例变量初始化的过程    静态块、非静态块、构造函数的加载顺序
 1 public class Baset {
2 private String baseName = "base";
3 // 构造方法
4 public Baset() {
5 callName();
6 }
7 // 成员方法
8 public void callName() {
9 // TODO Auto-generated method stub
10 System.out.println("basename:" + baseName);
11 }
12 //静态内部类
13 static class Sub extends Baset {//static必须写在开头
14 // 静态字段
15 private String baseName = "sub";
16 public Sub() {
17 callName();
18 }
19 // 重写父类的方法
20 public void callName() {
21 System.out.println("subname:" + baseName);
22 }
23 }
24
25 public static void main(String[] args) {
26 Baset base = new Sub();
27 }
28 }
[置顶]        Java 类的实例变量初始化的过程    静态块、非静态块、构造函数的加载顺序

 

求这段程序的输出。

解答此题关键在于理解和掌握类的加载过程以及子类继承父类后,重写方法的调用问题:

一、从程序的执行顺序去解答:

1.编译;当这个类被编译通知后,会在相应的目录下生成两个.class 文件。一个是 Base.class,另外一个就是Base$Sub.class。这个时候类加载器将这两个.class  文件加载到内存

2、Base base= new Sub():

  声明父类变量base对子类的引用,JAVA类加载器将Base,Sub类加载到JVM(Java虚拟机);

3、JVM为Base,Sub 的的成员开辟内存空间

  此时,Base 和Sub类中的值为null;

4、new Sub();

  这个时候会调用Sub类的隐式构造方法,

     Sub的构造方法本质为:

     public Sub(){

  super();//  调用父类的构造方法必须在构造方法中的第一行,为什么呢?这是因为在一些程序的升级中,要兼容旧版本的一些功能,父类即原先的一些初始化信息也要保证  //被执行到,然后执行当前

  baseName = "sub";//子类字段初始化

      }

    new Sub()执行到super()这行代码也就是跑到父类中去执行了,我们跳转到父类中的无参构造方法中执行,最后执行Sub()中的baseName = "sub"

5、public Base() ;

      父类无参构造方法的本质为:

  public Base(){

  baseName= "base";//父类字段初始化

  callName();

    }

  即将父类的baseName赋值为“base”,赋值后调用callName();

6、callName()方法在子类中被重写,因此调用子类的callName(),子类的callName方法执行,打印输出的是子类的baseName 字段的值,而这个时候子类的构造函数中字段的赋值还未执行。

7、父类的构造函数执行完毕,这个时候又回到子类当中,从super()的下一行继续执行,这个时候才为子类字段baseName 分配好存储空间,随后为其赋值:

 可见,在baseName = "sub"执行前,子类的callName()已经执行,所以子类的baseName为默认值状态null;

 、另写两个程序,进行更具体的分析

 1、第一个程序:

[置顶]        Java 类的实例变量初始化的过程    静态块、非静态块、构造函数的加载顺序
 1 public class InitialOrderTest {
2 // 变量
3 public String field = "变量";
4 // 静态变量
5 public static String staticField="静态变量";
6 //父类静态方法
7 public static void Order(){
8 System.out.print("父类静态方法");
9 System.out.println("staticField:"+staticField);
10 }
11
12 // 静态初始代码块
13 static{
14 System.out.println("静态初始化块");
15 System.out.println("staticField:"+staticField);
16 }
17 // 初始化代码块
18 {
19 System.out.println("初始化代码块");
20 System.out.println("field:"+field);
21 }
22 // 构造函数
23 public InitialOrderTest(){
24 System.out.println("构造器");
25
26 }
27
28 public static void main(String[] args) {
29 System.out.println("-----[[-------");
30 System.out.println(InitialOrderTest.staticField);
31 InitialOrderTest.Order();
32 System.out.println("------]]------");
33 InitialOrderTest i = new InitialOrderTest();
34 System.out.println("-----[[-------");
35 System.out.println(InitialOrderTest.staticField);
36 InitialOrderTest.Order();
37 System.out.println("------]]------");
38
39 }
40 }
[置顶]        Java 类的实例变量初始化的过程    静态块、非静态块、构造函数的加载顺序

执行结果为:

[置顶]        Java 类的实例变量初始化的过程    静态块、非静态块、构造函数的加载顺序

  第一个程序总结:

     1)、java中的块分为静态块(static{})和非静态块({}),这两种的执行是有区别的:

        非静态块的执行时间是:在执行构造函数之前。   静态块的执行时间是:class文件加载时执行。

        static类型的属性也是在类加载时执行的。

    2)可见Java类的实例变量初始化的过程:

       static类型的成员属性执行,静态块(static{})按顺序执行,然后非静态成员变量初始化,非静态代码块({})执行,最后执行构造方法。

       static类型与static块按先后顺序执行。

---------------------------------------------------------------------------------------------------------------------------------------------------

  2、第二个程序:

   

[置顶]        Java 类的实例变量初始化的过程    静态块、非静态块、构造函数的加载顺序
 1 public class BaseTest {
2 // 父类变量
3 private String baseName = "base";
4 // 父类静态变量
5 private static String staticField = "父类静态变量";
6 // 父类静态方法
7 public static void Order() {
8 System.out.println("父类静态方法-");
9 System.out.println("staticField:" + staticField);
10 }
11 // 父类静态初始代码块
12 static {
13 System.out.println("父类静态初始化代码块-");
14 System.out.println("staticField:" + staticField);
15 }
16 // 初始化代码块
17 {
18 System.out.println("父类非静态初始化代码块-");
19 System.out.println("baseName:" + baseName);
20 }
21 // 构造函数
22 public BaseTest() {
23 System.out.println("父类构造方法");
24 callName();
25 }
26 // 成员方法
27 public void callName() {
28 System.out.println("父类callName方法-");
29 System.out.println("baseName:" + baseName);
30 }
31
32 // 静态内部类
33 static class Sub extends BaseTest {
34 // 子类变量
35 private String baseName = "sub";
36 // 子类 静态变量
37 private static String staticField = "子类静态变量";
38
39 // 子类静态方法
40 public static void Order() {
41 System.out.println("子类静态方法-");
42 System.out.println("staticField:" + staticField);
43 }
44
45 // 子类静态初始化代码块
46 static {
47 System.out.println("子类静态初始化代码块-");
48 System.out.println("staticField:" + staticField);
49 }
50 // 子类非静态初始化代码块
51 {
52 System.out.println("子类非静态初始化代码块-");
53 System.out.println("baseName:" + baseName);
54 }
55
56 public Sub() {
57 System.out.println("子类构造方法");
58 callName();
59 }
60
61 public void callName() {
62 System.out.println("子类重写父类callName方法-");
63 System.out.println("baseName:" + baseName);
64 }
65 }
66
67 public static void main(String[] args) {
68
69 BaseTest b = new Sub();
70
71 System.out.println("-----[[-------");
72 Sub.Order();
73 System.out.println(Sub.staticField);
74 System.out.println(BaseTest.staticField);
75 BaseTest.Order();
76 System.out.println("------]]------");
77
78 }
79 }
[置顶]        Java 类的实例变量初始化的过程    静态块、非静态块、构造函数的加载顺序

 

  执行结果:

   [置顶]        Java 类的实例变量初始化的过程    静态块、非静态块、构造函数的加载顺序

第二个程序总结:

    1)可见Java初始化的顺序:   

[置顶]        Java 类的实例变量初始化的过程    静态块、非静态块、构造函数的加载顺序
 1  1 父类静态变量初始化
2 2 父类静态代码块-static{ }
3 3 子类静态变量初始化
4 4 子类静态代码块-static{ }
5 5 父类非静态变量初始化
6 6 父类非静态代码块-{}
7 7 父类构造方法
8 8 子类非静态变量初始化
9 9 子类非静态代码块-{}
10 10 子类构造方法
[置顶]        Java 类的实例变量初始化的过程    静态块、非静态块、构造函数的加载顺序