类的初始化过程


类的初始化过程

基本概念

类加载:在java代码中,类型(class,enum,interface)的加载、连接和初始化过程都是在程序运行期间完成的。这样提供了更大的灵活性,增加了更多的可能性
类加载器:JAVA源程序=》javac编译=》字节码文件.class=》JVM=》装载ClassLoader=》运行时数据区=》执行引擎,本地方法库接口=====》本地方法库
JVM基本结构:类加载器,执行引擎,运行时数据区,本地接口
类的装载:
  加载:查找并加载类的二进制数据
    类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class对象(规范并未说明Class对象位于哪里,在HotSpot虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构
  连接
    验证:确保被加载的类的正确性
    准备:为类的静态变量分配内存,并将其初始化为默认值
    解析:把类中的符号引用转换为直接引用
  初始化:为类的静态变量赋予正确的初始值
    Java虚拟机实现必须在每个类或接口被Java程序首次主动使用时才初始化它们。(对于什么时候类加载,java虚拟机规范中并没有进行强制约束,这点可以交给虚拟机的具体实现来自由把我,但是对于初始化阶段,虚拟机严格对定了以下几种情况)
    JAVA程序对类的使用方式可分为两种:主动使用(六种),被动使用
    主动使用
     1)创建类的实例,也就是new Object
     2)访问某个或接口的静态变量,或者对静态变量赋值
     3)访问类的静态方法
     4)反射调用类
     5)初始化一个类的子类
     6)Java虚拟机启动时被表明为启动类的类(如一个类中含有main方法)
    其他使用java类的方式都被看作为被动使用

主动使用样列

1、初始化一个类的子类回先初始化它的父类,但是初始化父类,子类并不会被初始化,需要牢牢把握主动使用的六种

public class Test1 {
    public static void main(String[] args) {
        //当只执行下面这条语句的时候,子类的static静态代码块并不会运行,也就是没有初始化,
        //因为对于类的初始化只有首次主动使用时才会初始化
        /**
         * 输出
         * MyParent1 static block
         * hello world
         */
         System.out.println(MyChild1.str);
//        System.out.println("=======================================");
//        System.out.println(MyChild1.str2);
    }

}
class MyParent1 {
    public static String str = "hello world";
    static {
        System.out.println("MyParent1 static block");
    }
}
class MyChild1 extends MyParent1 {
    public static String str2 = "welcome";
    static {
        System.out.println("MyChild1 static block");
    }
}

2、对于final常量的初始化过程

/**
 * 常量在编译阶段会存入到调用这个常量的方法所在类的常量池中,
 * 本质上,调用类并没有直接引用到定义常量的类,因此并不会触发定义常量的类的初始化
 * 注意:这里指的时将常量存存放到Test2的常量池中,之后Test2与MyParent2就没有任何关系了,
 *      甚至我们可以将MyParent2的Class文件删除
 */
public class Test2 {
    public static void main(String[] args) {
        //不会初始化MyParent2类
        System.out.println(MyParent2.str); //这里只会输出hellword,并不会导致MyParent2被初始化
    }
}
class MyParent2 {
    public static final String str = "hello world";
    static {
        System.out.println("MyParent2 static block");
    }
}
/**
 * 当一个常量的值并非编译器期间不可以确定的,那么其值就不会被放到调用该类的常量池中
 * 这时在程序运行时,会导致主动使用这个常量所在的类,显然会导致这个类被除初始化
 */
public class Test3 {
    public static void main(String[] args) {
        //由于str的值是编译期不能确定值的,所以会导致MyParent3被初始化
        System.out.println(MyParent3.str);//这里就回导致MyParent3被初始化
    }
}
class MyParent3 {
    //该值是在编译期间是不知道的,只有当运行时才能确定
    public static final String str = UUID.randomUUID().toString();
    static {
        System.out.println("MyParent3 static code");
    }
}

3、对于数组的初始化过程

/**
 * 对于数组实例来说,其类型是由JVM在运行期动态生成的,表示为[Lcom.chen.jvm.MyParent4
 * 这种形式。动态生成的类型,其父类型就是Object
 */
/**
 * 对于数组的加载javadoc
 * 数组类的加载器
 * <p> <tt>Class</tt> objects for array classes are not created by class
 * loaders, but are created automatically as required by the Java runtime.
 * The class loader for an array class, as returned by {@link
 * Class#getClassLoader()} is the same as the class loader for its element
 * type; if the element type is a primitive type, then the array class has no
 * class loader.
 */
public class Test4 {
    public static void main(String[] args) {
        //并不是主动使用,也就不会导致MyParent4类的初始化
        MyParent4[] myParent4 = new MyParent4[1];
        //[Lcom.chen.jvm.MyParent4;  这个类型是java虚拟机帮助我们动态生成的类型
        System.out.println(myParent4.getClass());//class [Lcom.chen.jvm.MyParent4;
        System.out.println(myParent4.getClass().getSuperclass());//class java.lang.Object
        MyParent4 myParent5 = new MyParent4();//MyParent4 statci code
        System.out.println(myParent5.getClass());//class com.chen.jvm.MyParent4

        int[] ints = new int[1];
        System.out.println(ints.getClass());//class [I
        System.out.println(ints.getClass().getSuperclass());//class java.lang.Object
    }
}
class MyParent4 {
    static {
        System.out.println("MyParent4 statci code");
    }
}

4、接口父子初始化过程

/**
 * 当一个接口初始化时,并不要求其父接口都完成初始化
 * 只有在真正使用到父接口的时刻(如引用接口中所定义的常量时),才会初始化
 */
public class Test5 {
    public static void main(String[] args) {
        //也就是MyParent5接口并不会被初始化
        System.out.println(MyChild5.b); //6
    }
}
interface MyParent5 {
    static int a = 5;
}
interface MyChild5 extends MyParent5 {
    static int b = 6;
}

5、静态变量初始化过程

public class Test6 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(Singleton.counter1); // 输出1
        //若new Singleton()放在counter1于counter2下面的话,也会输出1
        //这个实例可以典型说明类的初始化过程是先将静态变量赋予默认值,然后再赋正确的值这一过程
        System.out.println(Singleton.counter2); // 输出0
    }
}
class Singleton {
    private static Singleton singleton = new Singleton();
    public static int counter1;
    public static int counter2 = 0;
//    private static Singleton singleton = new Singleton();
    public Singleton() {
        counter1++;
        counter2++;
    }

    public static Singleton getInstance(){
        return singleton;
    }
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM