不面试不知道自己有多菜,遇到一个类初始化的面试题,写不出来只能亡羊补牢了
class Simple {
private static Simple simple = new Simple();
private ChildObject childObject = new ChildObject();
public Simple() {
System.out.println("Simple");
}
public static void main(String[] args) {
System.out.println("main Simple");
}
static {
System.out.println("static Simple");
}
}
public class ParentObject {
public ParentObject() {
System.out.println("父类构造函数");
}
}
public class ChildObject extends ParentObject {
public ChildObject() {
System.out.println("子类构造函数");
}
static {
System.out.println("子类静态代码块");
}
}
题目是写出输出,你可以试着写一下,答案我就放在最后了
静态变量,静态块,普通代码块,构造函数等在类初始化的执行顺序如下:
父类静态变量
父类静态代码块
子类静态变量
子类静态代码块
父类实例变量
父类代码块
父类构造函数
子类实例变量
子类代码块
子类构造函数
* 如果是第二次初始化则没有静态变量和静态代码块的初始化
上题中还存在一个难点就是类中定义了一个静态的自己的实例。
class Simple {
private static Simple simple = new Simple();
...
}
对于初始化阶段,虚拟机严格规范了有且只有5种情况下,必须对类进行初始化(只有主动去使用类才会初始化类):
- 当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
- 当jvm执行new指令时会初始化类。即当程序创建一个类的实例对象。
- 当jvm执行getstatic指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。
- 当jvm执行putstatic指令时会初始化类。即程序给类的静态变量赋值。
- 当jvm执行invokestatic指令时会初始化类。即程序调用类的静态方法。
- 使用
java.lang.reflect
包的方法对类进行反射调用时如Class.forname("..."),newInstance()等等。 ,如果类没初始化,需要触发其初始化。 - 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
- 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
- MethodHandle和VarHandle可以看作是轻量级的反射调用机制,而要想使用这2个调用, 就必须先使用findStaticVarHandle来初始化要调用的类。
本题就涉及到第4条。执行过程中会直接按静态变量,静态代码块的顺序初始化Simple类的信息。初始化静态变量时就会创建一个Simple的实例。这一步就相当于第二次调用Simple初始化的过程。仅会按实例变量,代码块,构造函数这个顺序初始化对应的Simple实例。结束初始化实例操作之后会继续静态变量,静态代码块的初始化任务。完成之后执行main函数。
问题的答案为
子类静态代码块
父类构造函数
子类构造函数
Simple
static Simple
main Simple
补充
class PrintObject {
public PrintObject() {
}
public PrintObject(String msg) {
System.out.println(msg);
}
}
class ParentObject {
private static PrintObject parentObject_ = new PrintObject("父类静态变量");
private PrintObject parentObject = new PrintObject("父类实例变量");
static {
new PrintObject("父类静态代码块");
}
{
new PrintObject("父类代码块");
}
public ParentObject() {
System.out.println("父类构造函数");
}
public ParentObject(String msg) {
System.out.println("父类构造函数");
}
}
class ChildObject extends ParentObject {
private static PrintObject parentObject_ = new PrintObject("子类静态变量");
private PrintObject parentObject = new PrintObject("子类实例变量");
static {
System.out.println("子类静态代码块");
}
{
new PrintObject("子类代码块");
}
public ChildObject() {
System.out.println("子类构造函数");
}
}
class Simple {
private static Simple simple = new Simple();
private ChildObject childObject = new ChildObject();
static {
System.out.println("static Simple");
}
{
System.out.println("block Simple");
}
public Simple() {
System.out.println("Simple");
}
public static void main(String[] args) {
System.out.println("main Simple");
new ChildObject();
}
}
输出
父类静态变量
父类静态代码块
子类静态变量
子类静态代码块
父类实例变量
父类代码块
父类构造函数
子类实例变量
子类代码块
子类构造函数
block Simple
Simple
static Simple
main Simple
父类实例变量
父类代码块
父类构造函数
子类实例变量
子类代码块
子类构造函数
.java文件被编译成.class文件后,普通代码块的内容被放进了构造函数里的开头,如打开ParentObject.class文件中的内容为
class ParentObject {
private static PrintObject parentObject_ = new PrintObject("父类静态变量");
private PrintObject parentObject = new PrintObject("父类实例变量");
public ParentObject() {
new PrintObject("父类代码块");
System.out.println("父类构造函数");
}
public ParentObject(String msg) {
new PrintObject("父类代码块");
System.out.println("父类构造函数");
}
static {
new PrintObject("父类静态代码块");
}
}