Java面试中经常遇到的类执行顺序

Java面试中经常遇到的类执行顺序

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/u012403290/article/details/68954928

引言:

记得以前面试的时候,会直接给一段代码,这段代码很多地方都有print,所以让我写出现打印出什么,后打印出什么。今天我整理一下单类的情况,继承的情况和一些特殊情况,希望大家看了之后,在面试过程中举一反三,成功规避错误。笔者目前整理的一些blog针对面试都是超高频出现的。大家可以点击链接:http://blog.csdn.net/u012403290

单类执行顺序:

下面是我写的一个demo:

package com.bw;

/**
 * @author brickworker
 * 关于类Color的描述:测试单个类的执行顺序问题
 */
public class Color {

    //构造函数
    public Color() {
        System.out.println("构造函数执行");
    }

    //静态代码块
    static{
        System.out.println("静态代码块执行");
    }

    //非静态代码块
    {
        System.out.println("非静态代码块执行");
    }


    //一般方法
    void run(){
        System.out.println("一般方法执行");
    }


    public static void main(String[] args) {
        System.out.println("main方法执行");//这个一定要写在最上面,程序一进入马上就执行,不然会导致结果不准确
        Color color = new Color();
        color.run();
    }



}
//执行结果:
//静态代码块执行
//main方法执行
//非静态代码块执行
//构造函数执行
//一般方法执行
      
      
     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

真的觉得是对的么?因为我们main的入口写在了类本身之中。导致结果其实是不准确的。我们换个方式,信建立一个Rundemo来触发执行:

package com.bw;

public class RunDemo {

    public static void main(String[] args) {
        System.out.println("main方法执行");//这个一定要写在最上面,程序一进入马上就执行,不然会导致结果不准确
        Color color = new Color();
        color.run();
    }
}
//执行结果:
//main方法执行
//静态代码块执行
//非静态代码块执行
//构造函数执行
//一般方法执行
      
      
     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

是吧,是不是main方法执行顺序跑到最前面去了呢?前面一种情况是因为main方法放在了目标类中,如果要执行这个main方法,虚拟机会先初始化这个主类,所以会先执行static静态代码块,关于这部分,以后我会写一篇关于虚拟机类加载机制的博文。总之,在看面试题的时候一定要看清楚main方法到底是放在哪里。

继承类执行顺序:

接下来,我们看看继承的情况是如何的,以下是我写的一个继承的demo,这里要强调一点,有写小伙伴为了看的更清楚,父类用system.err.println来打印,导致结果不同,两种输出方式是存在很大差别的,而且实验一定要建立在公平公正的前提下:


//父类
package com.bw;

/**
 * 
 * @author brickworker
 * 关于类Father的描述:测试继承执行顺序父类
 */
public class Father {

    //构造函数
    public Father() {
        System.out.println("父类构造函数执行");
    }

    //静态代码块
    static{
        System.out.println("父类静态代码块执行");
    }

    //非静态代码块
    {
        System.out.println("父类非静态代码块执行");
    }

    //一般方法
    void run(){
        System.out.println("父类一般方法");
    }
}


//子类
package com.bw;



/**
 * 
 * @author brickworker
 * 关于类Son的描述:测试继承执行顺序子类
 */
public class Son extends Father{

    //构造函数
    public Son() {
        System.out.println("子类构造方法");
    }

    //静态代码块
    static{
        System.out.println("子类静态代码块");
    }

    //非静态代码块
    {
        System.out.println("子类非静态代码块");
    }

    //子类重写一般方法
    @Override
    void run() {
        System.out.println("子类重写一般方法执行");
    }

    public static void main(String[] args) {
        System.out.println("main方法执行");
        Son son = new Son();
        son.run();
    }


}


//执行顺序:
//父类静态代码块执行
//子类静态代码块
//main方法执行
//父类非静态代码块执行
//父类构造函数执行
//子类非静态代码块
//子类构造方法
//子类重写一般方法执行
      
      
     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

现在,我们把main方法单独拿出来,和上面一样,看看执行结果:

main方法执行
父类静态代码块执行
子类静态代码块
父类非静态代码块执行
父类构造函数执行
子类非静态代码块
子类构造方法
子类重写一般方法执行
      
      
     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

总结一下:标准的执行顺序是:当前主程序>父类静态代码块>子类静态代码块>父类非静态代码块>父类构造函数>子类非静态代码块>子类构造方法>子类一般方法。那么把上面的顺序中关于父类执行的去掉,其实也符合我们前面讨论的单个类执行顺序。

注意了!注意了!(敲黑板),上面的也许大家在别的文章中看了不止一遍,那么下面的特殊情况你可能就不太清楚了,因为出现的少,但是我们一次看个透彻。

特殊执行顺序:

其实在类加载机制中,类加载存在主动加载和被动加载(可以不用知道,对下面文章理解不妨碍,只是希望知其然更要知其所以然),在被动加载过程中很多并不会触发初始化,所以在判断被动引用的时候,执行顺序会难很多,主要分为3中情况来具体说明:

一、通过子类调用了父类的静态字段,子类不会被初始化

//父类
package com.bw;

/**
 * 
 * @author brickworker
 * 关于类Father的描述:测试继承执行顺序父类
 */
public class Father {


    public static int father = 100;
    //静态代码块
    static{
        System.out.println("父类静态代码块执行");
    }

}
//子类
package com.bw;
/**
 * 
 * @author brickworker
 * 关于类Son的描述:测试继承执行顺序子类
 */
public class Son extends Father{


    static{
        System.out.println("子类静态代码块");
    }

}

//测试类:
package com.bw;

public class RunDemo {

    public static void main(String[] args) {
        System.out.println("main方法执行");
        System.out.println(Son.father);//用子类去调去父类的静态字段
    }

}
//执行结果:
//main方法执行
//父类静态代码块执行
//100


      
      
     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

从上面可以看出,子类的静态代码都没有执行,说明子类完全没有初始化。

扫描二维码关注公众号,回复: 7039659 查看本文章

二、类作为数组的组件类型不会触发类初始化:

借用上面单类的Color类:

package com.bw;

public class RunDemo {

    public static void main(String[] args) {
        System.out.println("main方法执行");
        Color[] colors = new Color[5];
    }

}
//执行结果:
//main方法执行
      
      
     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

上面用Color做为数组的组件,没有执行color的静态方法,说明也没有被初始化。

三、常量池引用也会导致不初始化类

package com.bw;

/**
 * @author brickworker
 * 关于类Color的描述:测试单个类的执行顺序问题
 */
public class Color {
    public static final String color = "red";//新增一段常量,编译阶段户直接放入常量池
    //静态代码块
    static{
        System.out.println("静态代码块执行");
    }
}

//
package com.bw;

public class RunDemo {

    public static void main(String[] args) {
        System.out.println("main方法执行");
        System.out.println(Color.color);
    }

}
//执行结果:
//main方法执行
//red


      
      
     
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

从上面的结果可以看出,虽然调用了red,但是并有执行color的静态代码块,说明它没有被初始化。

综上所述,如果把类加载时机和顺序进行结合,那么这样的面试题可能就会难倒一大批人,所以合理分析和总结是非常重要的。关于为什么后面类没有初始化,我想后面如果写java虚拟机类加载机制的时候我们再一起探究下。

好啦,如果你看到了这里,谢谢你对我辛苦劳动肯定,同时祝愿你学习进步,事业顺心。

猜你喜欢

转载自www.cnblogs.com/SanguineBoy/p/11363821.html
今日推荐