java虚拟机规范虽然没有强制性约束在什么时候开始类加载过程,但是对于类的初始化,虚拟机规范则严格规定了几种情况必须立即对类进行初始化,如果类没有进行过初始化,则需要先触发其初始化。在触发初始化是会有加载和连接(验证,准备,解析)阶段(不懂的看这个文章:https://blog.csdn.net/xiao1_1bing/article/details/81120787)。
生成这几种最常见的java代码场景是也称为主动使用:
1)使用new关键字实例化对象
2)访问类的静态变量,包括读取一个类的静态字段 和 设置一个类的静态字段 (除常量【 被final修辞的静态变量】 原因:常量一种特殊的变量,因为编译器把他们当作值(value)而不是域(field)来对待。如果你的代码中用到了常变量(constant variable),编译器并不会生成字节码来从对象中载入域的值,而是直接把这个值插入到字节码中。这是一种优化机制,叫编译器编译优化,对这个机制不理解可以看这两篇文章,文章1:https://blog.csdn.net/xiao1_1bing/article/details/81131837和文章2:https://blog.csdn.net/xiao1_1bing/article/details/81131300)
3)访问类的静态方法
4)反射 如( Class.forName(“my.xyz.Test”) )
5)当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化
6)虚拟机启动时,定义了main()方法的那个类先初始化
验证:
class ClassLoadTime{
static{
System.out.println("ClassLoadTime类初始化时就会被执行!");
}
public ClassLoadTime(){
System.out.println("ClassLoadTime构造函数!");
}
}
class ClassLoadDemo{
public static void main(String[] args){
ClassLoadTime clt = new ClassLoadTime();
}
}
先为第六种先初始化含有main()方法的ClassLoadDemo类,然后会加载static的main方法,同时发现第一种情况的要实例化(new)ClassLoadTime,(类在使用New调用创建的时候会被java类装载器装入)就需要初始化ClassLoadTime类,那么就会在该类的准备阶段为静态变量(类变量:为啥叫类变量?因为可以用类.静态变量名调用)和静态块分配空间和设置默认值。就会调用static块,最后因为new出的classLoadTime类参数为空,就会调用参数为空的构造函数最终生成对象。
输出结果:
ClassLoadTime类初始化时就会被执行!
ClassLoadTime构造函数!
class ClassLoadTime{
static{
System.out.println("ClassLoadTime类初始化时就会被执行!");
}
public static int max = 200; (防止测试类和此类不在一个包,使用public修饰符)
public ClassLoadTime(){
System.out.println("ClassLoadTime构造函数!");
}
}
class ClassLoadDemo{
public static void main(String[] args){
int value = ClassLoadTime.max;
System.out.println(value);
}
}
前面和上面一样,直到用类.静态变量,它会初始化ClassLoadTime类,初始化该类之前会有准备阶段:为static分配空间和设置默认值,就会调用static块和static变量。到了初始化阶段会将代码中的200赋值给max,所以会打印出200。因为并没有实例化该类,所以不会调用无参的构造方法,虚拟机只进行了类加载并没有生成对象,因此非静态成员(实例成员)并不会进行初始化,而静态成员(类成员)则被初始化。不new是不会调用构造器的。
输出:
ClassLoadTime类初始化时就会被执行!
200
class ClassLoadTime{
static{
System.out.println("ClassLoadTime类初始化时就会被执行!");
}
public static int max = 200; (防止测试类和此类不在一个包,使用public修饰符)
public ClassLoadTime(){
System.out.println("ClassLoadTime构造函数!");
}
}
class ClassLoadDemo{
public static void main(String[] args){
ClassLoadTime.max = 100;
}
}
这个和上面一样,但是该ClassLoadTime在初始化阶段将200赋值给max,但在使用阶段又将100赋值给静态变量max。
输出:
ClassLoadTime类初始化时就会被执行!
测试:
class ClassLoadTime{
static{
System.out.println("ClassLoadTime类初始化时就会被执行!");
}
public static final int MIN = 10; (防止测试类和此类不在一个包,使用public修饰符)
}
class ClassLoadDemo{
public static void main(String[] args){
System.out.println(ClassLoadTime.MIN);
}
}
由于编译器编译优化,导致该变量在常量池中,当你用ClassLoadTime.MIN时,只会到常量池中查该数值,不会加载ClassLoaderTime类。
输出:
10
class ClassLoadTime{
static{
System.out.println("ClassLoadTime类初始化时就会被执行!");
}
public static int max = 200; (防止测试类和此类不在一个包,使用public修饰符)
public ClassLoadTime(){
System.out.println("ClassLoadTime构造函数!");
}
public static void method(){
System.out.println("静态方法的调用!");
}
}
class ClassLoadDemo{
public static void main(String[] args){
ClassLoadTime.method();
}
}
这个和第二种情况有些类似,只要你调用类的静态方法,它的静态代码块和静态变量会在准备阶段分配内存和设置默认值。然后会调用静态方法。但是该类没有实例化,所以不会调用无参的构造方法。
输出:
ClassLoadTime类初始化时就会被执行!
静态方法的调用!
同时,会不会想到如果实例化(new)静态块和静态变量会被调用,但静态方法是否会被调用?看下面例子。
class ClassLoadTime{
static{
System.out.println("ClassLoadTime类初始化时就会被执行!");
}
public static int max = 200; //(防止测试类和此类不在一个包,使用public修饰符)
public ClassLoadTime(){
System.out.println("ClassLoadTime构造函数!");
}
public static void method(){
System.out.println("静态方法的调用!");
}
}
class ClassLoadDemo{
public static void main(String[] args){
ClassLoadTime c =new ClassLoadTime();
}
}
输出:
ClassLoadTime类初始化时就会被执行!
ClassLoadTime构造函数!
由此可见,除了对静态方法显示调用外,其他都不会调用静态方法。
class ClassLoadTime{
static{
System.out.println("ClassLoadTime类初始化时就会被执行!");
}
public static int max = 200; //(防止测试类和此类不在一个包,使用public修饰符)
public ClassLoadTime(){
System.out.println("ClassLoadTime构造函数!");
}
public static void method(){
System.out.println("静态方法的调用!");
}
}
class ClassLoadDemo{
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("ClassLoadTime");
}
}
对于反射只会初始化该类,准备阶段将静态块和静态变量进行分配空间和设置默认值,其他都不会做。
输出:
ClassLoadTime类初始化时就会被执行!
class Fu{
public static int value = 20;
static{
System.out.println("父类进行了类的初始化!");
}
}
class Zi extends Fu{
static{
System.out.println("子类进行了类的初始化!");
}
}
class LoadDemo{
public static void main(String[] args){
System.out.println(Zi.value);
}
}
通过上面的了解,基本已经掌握类的初始化步骤和顺序,但是对于继承关系来说,实例化子类的顺序是这样的(虽然调用父类构造函数但是不生成父类实例,因为如果父类是抽象类,那么它是不会被实例化的,但会调用构造函数):
先初始化父类的静态代码—>初始化子类的静态代码–>初始化父类的非静态代码—>初始化父类构造函数—>初始化子类非静态代码—>初始化子类构造函数
先将各自的静态代码加载进来,再返回到父类去加载其他的。但是当子类只调用父类静态的话,是不会调用子类的类加载,也不会调用父类的构造方法,只会初始化静态成员(静态块和静态方法)。
输出:
父类进行了类的初始化!
20
解析下这段代码:
class SingleTon {
private static SingleTon singleTon = new SingleTon();
// count1=1,count2=1
public static int count1;
// count1=1,count2=1(没进行初始化)
public static int count2 = 0;
// count1=1,count2=0
private SingleTon() {
count1++;
count2++;
}
public static SingleTon getInstance() {
return singleTon;
}
}
public class Test {
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}
结果:
count1=1
count2=0
分析原因
(对此不理解的看类加载的准备阶段和初始化阶段,地址:https://blog.csdn.net/xiao1_1bing/article/details/81120787):
java类中各种成员的初始化时机,此处不一一测试:
类变量(静态变量)、实例变量(非静态变量)、静态代码块、非静态代码块 的初始化时机:
(1)* 由 static 关键字修饰的(如:类变量[静态变量]、静态代码块)将在类被初始化创建实例对象之前被初始化,而且是按顺序从上到下依次被执行,static变量和static代码块优先级别是一样的,所以安装从上到下顺序;
public static int value =34;
static{
System.out.println("静态代码块!");
}
public 类名(){
System.out.println("构造函数!");
}
一旦这样写,在类被初始化创建实例对象之前会先初始化静态字段value,然后执行静态代码块,当实例化对象时会执行构造方法中的代码
(2)* 没有 static 关键字修饰的(如:实例变量[非静态变量]、非静态代码块)初始化实际上是会被提取到类的构造器中被执行的,但是会比类构造器中的代码块优先执行,其也是按顺序从上到下依次被执行。
public int value =34;
{
System.out.println("非静态代码块!");
}
public 类名(){
System.out.println("构造函数!");
}
在使用构造函数实例化一个对象时,会先初始化value,然后执行非静态代码块,最后执行构造方法里面的代码。
(3)*在存在父类的时候,调用子类的构造时,会先调用父类的默认构造(空参构造),进行父类的初始化,虽然调用了父类的构造函数,但是没有生成父类的实例。