/** 
     * 被动引用情景3 
     * 常量在编译阶段会被存入调用类的常量池中,本质上并没有引用到定义常量类类,所以自然不会触发定义常量的类的初始化 
     * @author root 
     * 
     */
    class ConstClass{ 
        static{ 
            System.out.println("ConstClass init."); 
        } 
        public final static String value="hello"; 
    } 
    
    public class test{ 
        public static void main(String[] args){ 
            System.out.println(ConstClass.value); 
        } 
    }

  输出结果:hello(tip:在编译的时候,ConstClass.value已经被转变成hello常量放进test类的常量池里面了)

  以上是针对类的初始化,接口也要初始化,接口的初始化跟类的初始化有点不同:

  上面的代码都是用static{}来输出初始化信息的,接口没法做到,但接口初始化的时候编译器仍然会给接口生成一个<clinit>()的类构造器,用来初始化接口中的成员变量,这点在类的初始化上也有做到。真正不同的地方在于第三点,类的初始化执行之前要求父类全部都初始化完成了,但接口的初始化貌似对父接口的初始化不怎么感冒,也是说,子接口初始化的时候并不要求其父接口也完成初始化,只有在真正使用到父接口的时候它才会被初始化(比如引用接口上的常量的时候啦)。

  下面分解一下一个类的加载全过程:加载->验证->准备->解析->初始化

  首先是加载:

  这一块虚拟机要完成3件事:

  1、通过一个类的全限定名来获取定义此类的二进制字节流。

  2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

  3、在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

  关于第一点,很灵活,很多技术都是在这里切入,因为它并没有限定二进制流从哪里来:

  从class文件来->一般的文件加载

  从zip包中来->加载jar中的类

  从网络中来->Applet

  ..........

  相比与加载过程的其他几个阶段,加载阶段可控性强,因为类的加载器可以用系统的,也可以用自己写的,程序猿可以用自己的方式写加载器来控制字节流的获取。

  获取二进制流获取完成后会按照jvm所需的方式保存在方法区中,同时会在java堆中实例化一个java.lang.Class对象与堆中的数据关联起来。