虽然每天都在用Java,现在做的项目后台也是用的Java语言,近期帮师兄做的算法实验也是用Java实现的,但是总觉得自己对这门语言学习的不够深入,仅仅停留在会用的层面上。今天看了一位中科大师姐的文章,觉得自己很有必要再深入学习这门语言,还需要加深自己对Java的理解。我个人觉得,对一个程序员来说,理解力十分重要。一门编程语言,学会使用很容易,但是要真正理解这门语言的设计哲学似乎就没那么容易了。而一但真正理解了,那么就会对自身有一个质的提升。
接下来就谈谈正题吧,Java类加载机制这个词相信对于任何一个使用过Java的人都不会陌生,但是并不是每一个写Java程序的人都十分清楚Java类是什么时候被加载的,是什么时候被初始化的,下面我们就来分析一下吧。
类的加载
首先要清楚,类的加载和类的初始化是不同的。类的加载是由类加载器完成的,类加载器也是一个程序,如ClassLoader类就是一个类加载器,它也是用Java语言写的,可以通过继承并重写它的方法来实现自己的类加载方式。常见的类加载方式是当用到这个类的时候,系统才加载这个类。一个JVM中可以有多个类加载器,如下图
类的初始化
而类的初始化发生在类加载完成之后,这时会把类的静态属性初始化。当类发生以下情况时也会初始化这个类:
- 通过使用new关键字创建实例
- 使用Class.forName()反射创建实例(有可能导致ClassNotFoundException)
- 类的静态方法被调用
- 类的静态域被赋值
- 类的静态域被访问,而且它不是常量(没有final修饰,被final关键字修饰的常量为编译时常量,不会触发类的初始化)
- 在顶层类中执行assert语句
类的初始化步骤
- 类按照代码从上到下的顺序初始化,所以声明在顶部的字段初始化早于底部的字段
- 超类的初始化早于子类和衍生类
- 如果类的初始化是由于访问静态域而触发,那么只有该声明静态域的类被初始化,而不会触发超类或者子类的初始化,即使该类的静态域被子类或子接口或者它的实现类所引用
- 接口初始化不会导致父接口的初始化
- 静态域的初始化在类的静态初始化期间,非静态域的初始化时在类的实例创建期间,这意味这静态域初始化在非静态域之前
- 非静态域通过构造器初始化,子类在做任何初始化之前构造器会隐含地调用父类的构造器
示例代码
定义父类
package org.colin.classinit;
public class SuperClass {
static {
System.out.println("SuperClass:Static block of Super class is initialized!");
}
{
System.out.println("SuperClass:Non static block of Super class is initialized!");
}
}
定义子类
package org.colin.classinit;
public class SubClass extends SuperClass {
static {
System.out.println("SubClass:Static block of Sub class is initialized!");
}
{
System.out.println("SubClass:Non static blocks of Sub class is initialized!");
}
}
定义一个不被使用的类
package org.colin.classinit;
public class NotUsedClass {
static {
System.out.println("NotUsedClass:NotUsedClass has been initialized!");
}
}
测试类
package org.colin.classinit;
public class ClassInitializationTest {
public static void main(String[] args) {
NotUsedClass notUsedClass = null;
SubClass subClass = new SubClass();
System.out.println((Object) notUsedClass == (Object) subClass);
}
}
输出结果为:
SuperClass:Static block of Super class is initialized!
SubClass:Static block of Sub class is initialized!
SuperClass:Non static block of Super class is initialized!
SubClass:Non static blocks of Sub class is initialized!
false
由此可见:
1. 超类初始化早于子类
2. 静态变量或代码块初始化早于非静态块和域
3. 没使用的类根本不会被初始化,因为他没有被使用
总结
Java类在第一次被用到的时候,被类加载器加载,接着初始化静态部分(被final关键字修饰的常量不会被初始化,因为它在编译的时候就已经确定了),如果有父类,会先进行父类的初始化(Java需要保证父类的成员初始化早于子类的成员初始化,否则,在子类中使用父类的成员变量就会出现问题)。当这个类被实例化的时候,会初始化类的非静态部分。因为静态部分是属于类本身的,类初始化就要初始化它。而非静态部分是属于具体实例的,所以在类被实例化时初始化。