面试或者笔试中经常会遇到的一道题目就是Java类中各部件加载的顺序,通常会给你一段代码让你写出输入的结果,一般包含:静态块、静态变量、普通代码块、构造器、子类重写父类的方法等等。下面我们来看一段代码:
Father类代码:
package com.extend;
public class Father {
public static int Father_A = getA();
int Father_C = getC();
{
System.out.println("我是Father的第一个普通代码块");
}
static{
System.out.println("我是Father的第一个静态块");
}
{
System.out.println("我是Father的第二个普通代码块");
}
static{
System.out.println("我是Father的第二个静态块");
}
public static boolean Father_B = getB();
public static int getA(){
System.out.println("我是Father获取静态变量A的方法");
return 100;
}
public static boolean getB(){
System.out.println("我是Father获取静态变量B的方法");
return true;
}
public int getC(){
System.out.println("我是Father获取变量C的方法");
return 100;
}
public Father(){
System.out.println("我是Father的构造器");
}
public void hello(){
System.out.println("Hello!我是Father");
}
}
Son类代码:
package com.extend;
public class Son extends Father {
public static int Son_A = getA();
int Son_C = getC();
{
System.out.println("我是Son的第一个普通代码块");
}
static{
System.out.println("我是Son的第一个静态块");
}
{
System.out.println("我是Son的第二个普通代码块");
}
static{
System.out.println("我是Son的第二个静态块");
}
public static boolean Son_B = getB();
public static int getA(){
System.out.println("我是Son获取静态变量A的方法");
return 100;
}
public static boolean getB(){
System.out.println("我是Son获取静态变量B的方法");
return true;
}
public Son(){
System.out.println("我是Son的构造器");
}
public void hello(){
System.out.println("Hello!我是Son");
}
}
接下来在看main方法(main方法在Son类中)如何执行:
public static void main(String[] args) {
System.out.println("main start");
System.out.println("-------分隔符--------");
Father s = new Son();
System.out.println("-------分隔符--------");
s.hello();
System.out.println("-------分隔符--------");
Father f = new Father();
System.out.println("-------分隔符--------");
f.hello();
System.out.println("-------分隔符--------");
}
想清楚后来看答案,是否和预想的一致呢?
↓
↓
↓
输出结果:
我是Father获取静态变量A的方法
我是Father的第一个静态块
我是Father的第二个静态块
我是Father获取静态变量B的方法
我是Son获取静态变量A的方法
我是Son的第一个静态块
我是Son的第二个静态块
我是Son获取静态变量B的方法
main start
-------分隔符--------
我是Father获取变量C的方法
我是Father的第一个普通代码块
我是Father的第二个普通代码块
我是Father的构造器
我是Father获取变量C的方法
我是Son的第一个普通代码块
我是Son的第二个普通代码块
我是Son的构造器
-------分隔符--------
Hello!我是Son
-------分隔符--------
我是Father获取变量C的方法
我是Father的第一个普通代码块
我是Father的第二个普通代码块
我是Father的构造器
-------分隔符--------
Hello!我是Father
-------分隔符--------
到这里可能很多同学要问了,为什么“main start”不是第一行输出呢?这是因为main方法虽然是主进程的入口方法,但是它也需要在类加载完之后才会执行main方法,所以main方法的第一行输出是在所有的静态变量、静态块加载之后执行。这里还有一个容易搞混的地方,在第二次实例化Father类的时候并不会输出Father类静态变量以及静态块中的内容,也就是说静态变量以及静态块只是在第一次实例化时会被加载。
单一类加载顺序:
- 静态变量、静态块先执行,如果存在多个则顺序执行;只会加载一次
- 如果main方法第一行有输出,则输出第一行内容
- 接下来加载普通变量、普通代码块,如果存在多个则顺序执行;每实例化一次就会加载一次
- 接下来是调用该类的构造方法;每实例化一次就会加载一次
继承类之间的加载顺序:
- 先加载父类的静态变量、静态块,如果存在多个则顺序执行;只会加载一次
- 再加载子类的静态变量、静态块,如果存在多个则顺序执行;只会加载一次
- 如果main方法第一行有输出,则输出第一行内容
- 接下来是加载父类的普通变量、普通代码块,如果存在多个则顺序执行;每实例化一次就回加载一次
- 接下来是调用父类的构造器;每实例化一次就会加载一次
- 接下来是加载子类的普通变量、普通代码块,如果存在多个则顺序执行;每实例化一次就会加载一次
- 接下来是调用子类的子类的构造器;每实例化一次就会加载一次
总之实例化的时候先加载静态相关信息,再加载普通相关信息,再加载构造器
静态块,静态变量 > 普通变量,普通代码块 > 构造器
在写这些代码的时候发现一个比较疑惑的问题,我在Father类中定义了一个普通变量father_C对应了一个getC()的方法,无论这个方法是静态方法还是普通方法,编译器都不会报错并且执行也不受影响。代码如下:
int Father_C = getC();
int Father_D = new Father().getD();
int Father_E = getD();
public static int getC(){
System.out.println("我是Father获取变量C的方法");
return 100;
}
public int getD(){
System.out.println("我是Father获取变量C的方法");
return 100;
}
这是编译器在编译阶段会优化还是什么原因呢?欢迎大家在留言区进行讨论。