Java代码块执行顺序初探

摘要

Java代码块执行顺序初探

1、仅构造方法

每个类都有构造方法。

如果没有显式地为类定义构造方法,Java编译器将会为该类提供一个默认构造方法。

若定义了有参构造方法,编译器将不会提供默认的无参构造,如需要,需要自己构造。

Java继承中对构造函数是不继承的,只是显式或者隐式调用。

实例

父类A

public class A {
 
    public A(){
        System.out.println("A constructor");
    }
 
 
    public A(int a){
        System.out.println("A constructor ,num:"+a);
    }
}

子类B

public class B extends A {
 
    public B (){
        System.out.println("B constructor");
    }
 
    public B(int b){
        System.out.println("B constructor ,num:"+b);
    }
}

测试类E

public class E {
    public static void main(String[] args) {
        System.out.println("E main");
        A a = new B();
        System.out.println("-------------AB可爱分割线-------------");
        B b = new B();
 
        System.out.println("\n---------小可爱分割线-----------\n");
 
        A a1 = new B(1);
        System.out.println("-------------AB可爱分割线-------------");
        B b1 = new B(2);
    }
}

测试结果

E main
A constructor
B constructor
-------------AB可爱分割线-------------
A constructor
B constructor
 
---------小可爱分割线-----------
 
A constructor
B constructor ,num:1
-------------AB可爱分割线-------------
A constructor
B constructor ,num:2

解析

Java继承中对构造函数是不继承的,只是显式或者隐式调用,并且必须是在构造函数第一行。这里是隐式调用了super()。

子类不能继承父类的构造器(构造方法或者构造函数),但是父类的构造器带有参数的,则必须在子类的构造器中显式地通过super关键字调用父类的构造器并配以适当的参数列表。

如果父类有无参构造器,则在子类的构造器中用super调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。

所以先执行父类构造函数,再执行子类构造函数。

2、静态块、构造方法

静态代码块:在java中使用static关键字声明的代码块。

实例1:

父类A

public class A {
 
    static {
        System.out.println("A constructor before static");
    }
 
    public A(){
        System.out.println("A constructor");
    }
 
 
    public A(int a){
        System.out.println("A constructor ,num:"+a);
    }
 
    static {
        System.out.println("A constructor after static");
    }
}

子类B

public class B extends A {
    static {
        System.out.println("B constructor before static");
    }
    public B (){
        System.out.println("B constructor");
    }
 
    public B(int b){
        System.out.println("B constructor ,num:"+b);
    }
 
    static {
        System.out.println("B constructor after static");
    }
}

测试类E

public class E {
    public static void main(String[] args) {
        System.out.println("E main");
        A a = new B();
        System.out.println("-------------AB可爱分割线-------------");
        B b = new B();
 
        System.out.println("\n---------小可爱分割线-----------\n");
 
        A a1 = new B(1);
        System.out.println("-------------AB可爱分割线-------------");
        B b1 = new B(2);
    }
}


测试结果

E main
A constructor before static
A constructor after static
B constructor before static
B constructor after static
A constructor
B constructor
-------------AB可爱分割线-------------
A constructor
B constructor
 
---------小可爱分割线-----------
 
A constructor
B constructor ,num:1
-------------AB可爱分割线-------------
A constructor
B constructor ,num:2

解析

静态块用于初始化类,为类的属性初始化。每个静态代码块只会执行一次。

由于JVM在加载类时会执行静态代码块,所以静态代码块先于主方法执行。

如果类中包含多个静态代码块,那么将按照"先定义的代码先执行,后定义的代码后执行"。

静态代码块不能存在于任何方法体内。

静态代码块不能直接访问静态实例变量和实例方法,需要通过类的实例对象来访问。

当父类与子类都有静态代码块和构造函数的时候,执行顺序如下:

  • 父类静态代码块 > 子类静态代码块(Java虚拟机加载类时,就会执行该块代码)。

  • 父类构造函数 > 子类构造函数 (先有父亲,后有孩子)

  • 如果是多级继承关系的话,高层的父类首先执行,然后依次递减。

总结:静态优先执行,父类优先于子类执行。 静态代码块是在JVM加载类的时候执行的,而且静态代码块执行且仅执行一次

实例2:

子类包含静态属性---子类的实例化

父类A:同实例1


子类B:

 

public class B extends A {
 
    public static A b = new B(3);
    public static A a = new A(2);
    public static B c = new B(1);
    static {
        System.out.println("B constructor before static");
    }
    public B (){
        System.out.println("B constructor");
    }
 
    public B(int b){
        System.out.println("B constructor ,num:"+b);
    }
 
    static {
        System.out.println("B constructor after static");
    }
}

测试类F

public class F {
    public static void main(String[] args) {
        System.out.println("F main");
        A a = new B();
    }
}


测试结果

F main
A constructor before static
A constructor after static
A constructor
B constructor ,num:3
A constructor ,num:2
A constructor
B constructor ,num:1
B constructor before static
B constructor after static
A constructor
B constructor


解析
进入main函数,先执行

System.out.println("F main");

得到:      F main

A a = new B();


A constructor before static
A constructor after static


执行子类静态成员变量(方法块),由于子类包含静态属性和方法,按照“先定义的代码先执行,后定义的代码后执行”的规则,先执行静态属性部分,即:

 public static A b = new B(3);
 public static A a = new A(2);
 public static B c = new B(1);

此时由于父类静态部分已经执行,故执行相应的构造函数得到(感觉说是中断类加载过程,执行实例初始化过程更贴切些,这部分可以考虑参考看最后关于“暂停类加载”部分):


A constructor
B constructor ,num:3
A constructor ,num:2
A constructor
B constructor ,num:1

由于之后子类含有静态块,故继续执行静态块中的打印:


B constructor before static
B constructor after static

最后执行最终的无参构造函数:


A constructor
B constructor

若父类A,改成如下会更直观:

public class A {
 
   public static A a = new B(12);
 
   static {
       System.out.println("A constructor before static");
   }
 
   public A(){
       System.out.println("A constructor");
   }
 
 
   public A(int a){
       System.out.println("A constructor ,num:"+a);
   }
 
   static {
       System.out.println("A constructor after static");
   }
}

此时结果:

F main
A constructor
B constructor ,num:12
A constructor before static
A constructor after static
A constructor
B constructor ,num:3
A constructor ,num:2
A constructor
B constructor ,num:1
B constructor before static
B constructor after static
A constructor
B constructor

实例3:

子类和父类存在相同的静态方法和非静态方法时

父类A

public class A {
 
   static {
       System.out.println("A constructor before static");
   }
 
   public static void getClassStaticFunc(){
       System.out.println("get A static Function");
   }
 
   public  void getClassFunc(){
       System.out.println("get A  Function");
   }
 
   public A(){
       System.out.println("A constructor");
   }
 
 
   public A(int a){
       System.out.println("A constructor ,num:"+a);
   }
 
   static {
       System.out.println("A constructor after static");
   }
 
}

子类B

public class B extends A {
 
   static {
       System.out.println("B constructor before static");
   }
 
   public static void getClassStaticFunc(){
       System.out.println("get B static Function");
   }
 
   public  void getClassFunc(){
       System.out.println("get B  Function");
   }
 
   public B (){
       System.out.println("B constructor");
   }
 
   public B(int b){
       System.out.println("B constructor ,num:"+b);
   }
 
   static {
       System.out.println("B constructor after static");
   }
 
}

测试类F

public class F {
   public static void main(String[] args) {
       System.out.println("F main");
       A a = new B();
       a.getClassStaticFunc();
       a.getClassFunc();
   }
}

测试结果

F main
A constructor before static
A constructor after static
B constructor before static
B constructor after static
A constructor
B constructor
get A static Function
get B  Function

解析
java中静态属性和静态方法可以被继承,但是没有被重写(overwrite)而是被隐藏。

原因:

1). 静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成对,不需要继承机制及可以调用。如果子类里面定义了静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法或变量名完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。
2). 多态之所以能够实现依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。
3). 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。

3、构造代码块、静态块、构造方法

构造块:直接在类中定义且没有加static关键字的代码块称为{}构造代码块。

构造代码块在创建对象时被调用,每次创建对象都会被调用,并且构造代码块的执行次序优先于类构造函数。

实例1

父类A

public class A {
 
   {
       System.out.println("A constructor before static before 构造块");
   }
   static {
       System.out.println("A constructor before static");
   }
 
   public A(){
       System.out.println("A constructor");
   }
 
 
   public A(int a){
       System.out.println("A constructor ,num:"+a);
   }
 
   static {
       System.out.println("A constructor after static");
   }
 
   {
       System.out.println("A constructor after static after 构造块");
   }
}

子类B


public class B extends A {
 
   {
       System.out.println("B constructor before static before 构造块");
   }
 
   static {
       System.out.println("B constructor before static");
   }
   public B (){
       System.out.println("B constructor");
   }
 
   public B(int b){
       System.out.println("B constructor ,num:"+b);
   }
 
   static {
       System.out.println("B constructor after static");
   }
 
   {
       System.out.println("B constructor after static after 构造块");
   }
}

测试类E

public class E {
    public static void main(String[] args) {
        System.out.println("E main");
        A a = new B();
        System.out.println("-------------AB可爱分割线-------------");
        B b = new B();
 
        System.out.println("\n---------小可爱分割线-----------\n");
 
        A a1 = new B(1);
        System.out.println("-------------AB可爱分割线-------------");
        B b1 = new B(2);
    }
}

测试结果

E main
A constructor before static
A constructor after static
B constructor before static
B constructor after static
A constructor before static before 构造块
A constructor after static after 构造块
A constructor
B constructor before static before 构造块
B constructor after static after 构造块
B constructor
-------------AB可爱分割线-------------
A constructor before static before 构造块
A constructor after static after 构造块
A constructor
B constructor before static before 构造块
B constructor after static after 构造块
B constructor
 
---------小可爱分割线-----------
 
A constructor before static before 构造块
A constructor after static after 构造块
A constructor
B constructor before static before 构造块
B constructor after static after 构造块
B constructor ,num:1
-------------AB可爱分割线-------------
A constructor before static before 构造块
A constructor after static after 构造块
A constructor
B constructor before static before 构造块
B constructor after static after 构造块
B constructor ,num:2

解析
先静态后构造块,先父类后子类。

实例2:

对于非继承类


public class J {
    public static int k = 0;
    static {
        print("静态块2");
    }
    public static J t1 = new J("t1");
    public static J t2 = new J("t2");
    public static int i = print("i");
    public static int n = 99;
    public int j = print("j");
 
    {
        print("构造块");
    }
    static {
        print("静态块");
    }
 
    public J(String str) {
        System.out.println((++k) + ":" + str + "   i=" + i + "    n=" + n);
        ++i;
        ++n;
    }
 
    public static int print(String str) {
        System.out.println((++k) + ":" + str + "   i=" + i + "    n=" + n);
        ++n;
        return ++i;
    }
 
    public static void main(String args[]) {
        J t = new J("init");
    }
}

测试结果

1:静态块2   i=0    n=0
2:j   i=1    n=1
3:构造块   i=2    n=2
4:t1   i=3    n=3
5:j   i=4    n=4
6:构造块   i=5    n=5
7:t2   i=6    n=6
8:i   i=7    n=7
9:静态块   i=8    n=99
10:j   i=9    n=100
11:构造块   i=10    n=101
12:init   i=11    n=102

解析
典型的暂停类加载。即:类加载过程中,可能调用了实例化过程(因为static可以修饰方法,属性,代码块,内部类),此时则会暂停类加载过程而先执行实例化过程(被打断),执行结束再进行类加载过程。

类加载过程,不涉及构造方法。
实例化过程,涉及构造方法。

先类加载,执行static修饰的部分,遇到属性实例化,中断去执行实例化,完成后继续类加载,如此循环直至结束。

小结

无继承的的初始化顺序

静态成员变量(静态代码块)→普通成员变量→构造器

有继承的初始化顺序

父类静态成员变量、静态代码块→子类静态成员变量、静态代码块→父类普通成员变量、普通代码块→父类构造器→子类普通成员变量、普通代码块→子类构造器。

其他

类加载过程,不涉及构造方法

实例化过程,涉及构造方法

1、类中所有属性的默认值(一举而成)

2、父类静态属性初始化,静态块,静态方法的声明(按出现顺序执行)

3、子类静态属性初始化,静态块,静态方法的声明 (按出现顺序执行)

4 、调用父类的构造方法,

首先父类的非静态成员初始化,构造块,普通方法的声明(按出现顺序执行)

然后父类构造方法

5、 调用子类的构造方法,

首先子类的非静态成员初始化,构造块,普通方法的声明(按出现顺序执行)

然后子类构造方法


参考资料

深入了解Java程序执行顺序

Java中普通代码块,构造代码块,静态代码块区别及代码示例

Java:构造器,构造代码块,静态代码块的执行顺序

Java 继承

在继承中的问题,关于静态代码块,子类和父类的静态代码块的执行情况

JAVA静态方法是否可以被继承?

转载https://windcoder.com/javadaimakuaizhixingshunxuchutan

IT家园
IT家园

网友最新评论 (0)

发表我的评论
取消评论
表情