面试题
public class A {
static {
System.out.println("static a");
}
{
System.out.println("block a");
}
public static String name = "hello";
public static final String WORLD = "world";
public A() {
System.out.println("construct a");
}
}
public class B extends A {
static {
System.out.println("static b");
}
{
System.out.println("block b");
}
public B() {
System.out.println("construct b");
}
}
public class ClassLoadingDemo {
public static void main(String[] args) {
B b = new B();
B b2 = new B();
}
}
输出结果
static a
static b
block a
construct a
block b
construct b
block a
construct a
block b
construct b
从结果可以看到:
1.类初始化发生在对象初始化之前;
2.类初始化只发生一次;
3.父类初始化先于子类初始化;
4.静态代码块>代码块>构造方法;
类加载时机
下面内容引用自《深入理解Java虚拟机》
主动引用
虚拟机规范规定有且只有5种情况必须对类进行初始化:
1.遇到new、getstatic、putstatic、invokestatic四个字节码指令时。对应的java场景是:new对象时、读取或设置类变量(被final修饰的常量除外)时、调用类的静态方法时。
2.java.lang.reflect包的方法对类进行反射调用时
3.初始化类时,发现其父类没有初始化,则先初始化父类
4.虚拟机启动时指定的main()所在的类,虚拟机先初始化该类
5.如果java.lang.invoke.MethodHandle实例最后解析的结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄。并且这个方法句柄所对应的类没有初始化时,则先触发初始化。
被动引用
1.通过子类引用父类的静态字段,不会触发子类初始化
代码以上述为基础
public static void main(String[] args) {
System.out.println(B.name);
}
显示,jvm参数加上-XX:+TraceClassLoading
查看加载的类,可以看到B也加载了。
static a
hello
2.通过数组来引用类,不会触发此类的初始化
下面代码可以加载A,但是不初始化类
public static void main(String[] args) {
A[] a = new A[10];
}
3.常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
public static void main(String[] args) {
System.out.println(A.WORLD);
}
显示
world