Java类的初始化
我们知道一个类(class)要被使用必须经过装载,连接。初始化这种过程。以下先对这三阶段做一个简单的描写叙述。之后会结合一个简单的样例来说明java中类的初始化过程。
在装载阶段,类装载器(Bootstrap ClassLoader 或者用户自己定义的ClassLoader) 把编译形成的class文件加载内存。创建类相关的Class对象,这个Class对象封装了我们要使用的类的类型信息。
连接阶段又能够分为三个子步骤:验证、准备和解析。
验证就是要确保java类型数据格式 的正确性,并适于JVM使用。
准备阶段,JVM为静态变量分配内存空间,并设置默认值,注意。这里是设置默认值。比方说int型的变量会被赋予默认值0 。在这个阶段。JVM可能还会为一些数据结构分配内存。目的 是提高运行程序的性能,比方说方法表。
解析过程就是在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用。这个阶段能够被推迟到初始化之后,当程序运行的过程中真正使用某个符号引用的时候 再去解析它。
类会在首次被“主动使用”时运行初始化。为类(静态)变量赋予正确的初始值。在Java代码中,一个正确的初始值是通过类变量初始化语句或者静态初始化块给出的。而我们这里所说的主动使用 包含:
1. 创建类的实例
2. 调用类的静态方法
3. 使用类的非常量静态字段
4. 调用Java API中的某些反射方法
5. 初始化某个类的子类
6. 含有main()方法的类启动时
初始化一个类包含两个步骤:
1、 假设类存在直接父类的话。且直接父类还没有被初始化,则先初始化其直接父类
2、 假设类存在一个初始化方法,就运行此方法
注:初始化接口并不须要初始化它的父接口。
好,以下我们通过一些简单的样例来重点看一下初始化的过程 。
好,先上代码(超级简单的代码)。
- package kevin.demo;
- class Base {
- int a;
- static int b;
- static int c = 1;
- static {
- b=2;
- c = 3;
- System.out.println("Base:static init block invoked..");
- }
- void display() {
- System.out.println("Base:a=" + a + ",b=" + b + ",c=" + c);
- }
- }
- class Derived extends Base {
- int d;
- static int e = 4;
- static {
- e= 5;
- System.out.println("Derived:static init block invoked...");
- }
- void display() {
- super.display();
- System.out.println("Derived:d=" + d + ",e=" + e);
- }
- }
好,这里我们定义了一个简单的基类Base。然后定义了一个类Derived继承它。
这个已经简单到我不知道说什么了,直接上main方法吧。
- public class Demo {
- public static void main(String[] args) {
- new Derived().display();
- }
- }
好,我们运行这种方法,看会输出什么。。上个图看一下:
如图所看到的。尽管我们调用的子类的构造函数创建对象 。可是父类中的有关初始化的语句和静态初始化块也会被运行或调用,这正与前面所说的:假设一个类的直接父类还没有被初始化,那么 先初始化它的父类。而为什么这些初始化的动作会运行,就是由于我们主动使用了Derived这个类。而我们上面列出了好几个主动使用的情况。好以下我们改动一下main方法,看上面所言是否正确。
注:由于我们在这里谈论的主题是类的初始化,所以关于对象的初始化过程我们这里暂不讨论。
好。看下改动后的Demo:
- public class Demo {
- static int f;
- static{
- f=100;
- System.out.println("Demo:static init block invoked..and f="+f);
- }
- public static void main(String[] args) {
- System.out.println("maininvoked..");
- }}
好,看运行结果:
好,我们能够看到,含有main函数的类会首先运行初始化。
- public class Demo {
- static int f;
- static{
- f=100;
- System.out.println("Demo:static init block invoked..and f="+f);
- }
- public static void main(String[] args) {
- System.out.println(Derived.e);
- }
- }
以下再看一下,使用Derived的静态变量会不会引起初始化过程。。
果然,这也属于主动使用的一种,所以类运行了初始化,而且这里看的更加清楚 。含有main方法的类首先被初始化,由于它是第一个被使用的。但要注意。这里的静态就是不能是final的。否则初始化不会运行。
我们在main方法中加入一个语句,例如以下
- public static void main(String[] args) {
- System.out.println(Derived.e);
- new Derived().display();
- }
好。 这下看一下这个运行结果,能过这个,我主要 是想证明,类的初始化是仅仅运行一次呢还是每次主动使用类时都运行。。
通过这个截图我们能够看到,我们加入了一条语句 。但输出结果仅仅加入了两行。调用Derived类的实例的display()方法仅仅是运行了方法中的语句 ,并没有再运行初始化语句,因此我们能够总结出。一个类仅仅有会被初始化一次。除非是它被又一次装载。
Last but not least。让我们看一下静态嵌套类的初始化情况,它是要被主动使用的时候才初始化呢,还是它的外围类被主动使用的时候 它就会被初始化呢?
为了方便,我们就在Demo类中加入一个静态嵌套类Inner,例如以下
- public class Demo {
- static int f;
- static {
- f = 100;
- System.out.println("Demo:static init block invoked..and f=" + f);
- }
- static class Inner{
- static int g ;
- static{
- g=666;
- System.out.println("Inner:static init block invoked..and g="+g);
- }
- }
- public static void main(String[] args) {
- }
- }
看下main运行的结果:
我们能够看到,Inner类的静态初始化块并没有被运行。也就是说它并没有被初始化。好。如今在main函数中加上以下这句再看下运行结果:
- System.out.println(Inner.g);
看截图:
这下,非常明显,Inner的初始化进行了。所以说,它与外围类没有必定的联系,外围类的初始化并不能导致它被初始化, 要初始化静态嵌套类必须主动使用它,而主动使用其外围类并不等同于主动使用Static Nested Class.
好,上面就是我所知道的关于类的初始化的东西了。假设有错误的地方还请高手指点。我及时改动。。