接口初始化规则
例1
package com.study.classloader;
public class MyTest5 {
public static void main(String[] args) {
System.out.println(MyChild5.b);
}
}
interface MyParent5{
public static int a = 5;
}
interface MyChild5 extends MyParent5 {
public static int b = 6;
}
结果
6
由于在接口中无法像类一样添加静态代码块,并根据其是否被执行知道该类有没有类初始化,所以这里采取将MyParent5.class文件删除再执行的方式,看能否正常执行MyTest5的main方法
删除后再执行,执行结果
6
结论:
当一个接口在初始化时并不要求其父接口都完成了初始化
进一步,把MyChild5.class也同时删除,再执行,执行结果:
6
查看反编译结果
➜ jvm javap -c target.classes.com.study.classloader.MyTest5
警告: 文件 ./target/classes/com/study/classloader/MyTest5.class 不包含类 target.classes.com.study.classloader.MyTest5
Compiled from "MyTest5.java"
public class com.study.classloader.MyTest5 {
public com.study.classloader.MyTest5();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: bipush 6
5: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
8: return
}
结论:
接口本身定义的变量默认就是public static final的常量!!!所以,已经被保存于调用方法所在类MyTest5的常量池中
运行期常量
例2
package com.study.classloader;
import java.util.Random;
public class MyTest5 {
public static void main(String[] args) {
System.out.println(MyChild5.b);
}
}
interface MyParent5{
public static int a = 5;
}
interface MyChild5 extends MyParent5 {
public static int b = new Random().nextInt(2);
}
执行结果:
1
删除MyParent5.class与MyChild5.class,再次执行
例3
package com.study.classloader;
import java.util.Random;
public class MyTest5 {
public static void main(String[] args) {
System.out.println(MyChild5.b);
}
}
interface MyParent5{
public static int a = new Random().nextInt(10);
}
interface MyChild5 extends MyParent5 {
public static int b = new Random().nextInt(2);
}
执行结果:
0
删除MyParent5.class
再次执行:
结果:
只有在真正使用到父接口的时候(如引用接口中所定义的常量时),才会初始化
类与接口在初始化化时候的区别
例4
package com.study.classloader;
import java.util.Random;
public class MyTest5 {
public static void main(String[] args) {
System.out.println(MyChild5.b);
}
}
interface MyParent5{
public static int a = new Random().nextInt(10);
}
interface MyChild5 extends MyParent5 {
public static int b = 5;
}
删除MyParent5.class并执行
结果:
5
例5
package com.study.classloader;
import java.util.Random;
public class MyTest5 {
public static void main(String[] args) {
System.out.println(MyChild5.b);
}
}
class MyParent5{
public static int a = new Random().nextInt(10);
}
class MyChild5 extends MyParent5 {
public static int b = 5;
}
删除MyParent5.class并执行
结果:
结论:
接口默认所有变量都为final的常量
类在初始化的时候要求它的父类一定被初始化,但是接口不要求,而是真正使用父接口的时候才初始化父接口(如引用接口中的常量)
类加载器准备阶段和初始化阶段的重要意义分析
例6
package com.study.classloader;
public class MyTest6 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println("counter1 : " + Singleton.counter1);
System.out.println("counter2 : " + Singleton.counter2 );
}
}
class Singleton{
public static int counter1;
public static int counter2 = 0;
private static Singleton singleton = new Singleton();
private Singleton(){
counter1++;
counter2++;
}
public static Singleton getInstance(){
return singleton;
}
}
结果:
1
1
例7
package com.study.classloader;
public class MyTest6 {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println("counter1 : " + Singleton.counter1);
System.out.println("counter2 : " + Singleton.counter2 );
}
}
class Singleton{
public static int counter1;
private static Singleton singleton = new Singleton();
private Singleton(){
counter1++;
counter2++;
}
public static int counter2 = 0;
public static Singleton getInstance(){
return singleton;
}
}
结果:
1
1
counter1 : 1
counter2 : 0
分析:
在调用getInstance()类的静态方法后,类因为首次主动使用进行初始化。
连接阶段的准备阶段,为类中的静态变量赋了初值,其中singleton赋值为null。这时候counter1和counter2都赋值为0。
接下来进入初始化阶段,初始化是按照声明变量从上至下的顺序执行的。
1)给singleton赋值,new对象,这个过程调用了构造方法
2)执行构造方法,给counter1自增1,为1。给counter2自增1,为1。
3)给counter2赋值0