类的初始化以及实例化

在类的初始化阶段,虚拟机对类进行初始化,主要对类变量进行初始化。在java中对类变量进行初始化有两种方式:

①声明类变量时指定初始值
②使用静态初始化块为类变量指定初始化值
声明变量时指定初始值,静态初始化代码块**都被当成类的初始化语句**,他们无先后关系,谁写在前面就先初始化谁。
如果没有在这种方式中对类变量进行显式初始化,他将采用默认初始值(类准备阶段实现)。如下面a=5,b=6,c=0. 

(这里可以先了解类的声明周期https://zhuanlan.zhihu.com/p/67991761)

public class Test{
static int a =5;
static int b;
static int c;
static{
b=6;}
}

如果既在声明时指定初始值又在静态初始化块为其赋值,那么当类初始化完成后,会取最后一次赋值的结果。如下面b=9

public class Test{
static int a =5;
static int b=6;
static int c;
static{
b=9;}
}

JVM初始化一个类的步骤:

1.如果这个类还没有被加载和连接,程序先加载并连接该类
2.如果该类的父类还没有被初始化,则先初始化其直接父类
(其直接父类的初始化顺序也按这三个步骤,因此JVM总是最先初始化Object类,即当程序主动使用任何一个类,系统都会保证该类及其所有父类包括间接父类都已初始化)
3.如果系统中有初始化语句(声明时赋值,静态初始化块),系统依次执行这些初始化语句。

什么时候类会进行初始化?

当通过下面6种方式使用某个类或者接口的时候系统就会初始化这个类或者接口
1.调用某个类的类方法
2.访问某个类或接口的类变量或者为类变量赋值
(有特殊例子,被final修饰,已在编译器把结果放入常量池的静态字段除外

3.创建类的实例时(三种方式)
①new
②通过反射创建实例
③通过反序列化创建实例
4.通过反射方式强制创建某个类或接口对应的java.lang.Class对象(例如当用Class.forName(“Person”)创建class对象时,它会要求该类已经初始化,如果还没初始化会自动执行初始化)
5.初始化某个类的子类(因为初始化某个类的子类,其所有父类会初始化)
6.直接用java.exe命令运行某个主类(运行某个主类时,程序会先初始化这个主类)
以上场景中的行为称为对一个类进行 主动引用。剩下的引用类的方式一般都不会触发初始化,称为被动引用。

特别需要指出的是,类的实例化与类的初始化是两个完全不同的概念:

类的实例化是指创建一个类的实例(对象)的过程;
类的初始化是指为类中各个类成员(被static修饰的成员变量)赋初始值的过程,是类生命周期中的一个阶段。

被动引用的几种经典场景

1)、通过子类引用父类的静态字段,不会导致子类初始化
在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。在实例化一个对象时,JVM首先会检查相关类是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化

public class SSClass{
    static{
        System.out.println("SSClass");
    }
}  

public class SClass extends SSClass{
    static{
        System.out.println("SClass init!");
    }

    public static int value = 123;

    public SClass(){
        System.out.println("init SClass");
    }
}

public class SubClass extends SClass{
    static{
        System.out.println("SubClass init");
    }

    static int a;

    public SubClass(){
        System.out.println("init SubClass");
    }
}

public class NotInitialization{
    public static void main(String[] args){
        System.out.println(SubClass.value);
    }
}/* Output: 
        SSClass
        SClass init!
        123     
 *///:~

对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。在本例中,由于value字段是在类SClass中定义的,因此该类会被初始化;此外,在初始化类SClass时,虚拟机会发现其父类SSClass还未被初始化,因此虚拟机将先初始化父类SSClass,然后初始化子类SClass,而SubClass始终不会被初始化
2)、通过数组定义来引用类,不会触发此类的初始化

public class NotInitialization{
    public static void main(String[] args){
        SClass[] sca = new SClass[10];
    }
}

上述案例运行之后并没有任何输出,说明虚拟机并没有初始化类SClass。但是,这段代码触发了另外一个名为[Lcn.edu.tju.rico.SClass的类的初始化。从类名称我们可以看出,这个类代表了元素类型为SClass的一维数组,它是由虚拟机自动生成的,直接继承于Object的子类,创建动作由字节码指令newarray触发。
 3)、常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化

public class ConstClass{

    static{
        System.out.println("ConstClass init!");
    }

    public static  final String CONSTANT = "hello world";
}

public class NotInitialization{
    public static void main(String[] args){
        System.out.println(ConstClass.CONSTANT);
    }
}/* Output: 
        hello world
 *///:~

上述代码运行之后,只输出 “hello world”,这是因为虽然在Java源码中引用了ConstClass类中的常量CONSTANT,但是编译阶段将此常量的值“hello world”存储到了NotInitialization常量池中,对常量ConstClass.CONSTANT的引用实际都被转化为NotInitialization类对自身常量池的引用了。也就是说,实际上NotInitialization的Class文件之中并没有ConstClass类的符号引用入口,这两个类在编译为Class文件之后就不存在关系了。

总结

(原文链接:https://blog.csdn.net/justloveyou_/article/details/72466105)

1、一个实例变量在对象初始化的过程中会被赋值几次?

我们知道,JVM在为一个对象分配完内存之后,会给每一个实例变量赋予默认值,这个时候实例变量被第一次赋值,这个赋值过程是没有办法避免的。如果我们在声明实例变量x的同时对其进行了赋值操作,那么这个时候,这个实例变量就被第二次赋值了。如果我们在实例代码块中,又对变量x做了初始化操作,那么这个时候,这个实例变量就被第三次赋值了。如果我们在构造函数中,也对变量x做了初始化操作,那么这个时候,变量x就被第四次赋值。也就是说,在Java的对象初始化过程中,一个实例变量最多可以被初始化4次。

2、类的初始化过程与类的实例化过程的异同?

类的初始化是指类加载过程中的初始化阶段对类变量按照程序猿的意图进行赋值的过程;而类的实例化是指在类完全加载到内存中后创建对象的过程。

3、假如一个类还未加载到内存中,那么在创建一个该类的实例时,具体过程是怎样的?

我们知道,要想创建一个类的实例,必须先将该类加载到内存并进行初始化,也就是说,类初始化操作是在类实例化操作之前进行的,但并不意味着:只有当前类初始化操作结束后(可以在初始化时)才能进行类实例化(实例初始化)操作。

public class StaticTest {
    public static void main(String[] args) {
        staticFunction();
    }

    static StaticTest st = new StaticTest();

    static {   //静态代码块
        System.out.println("1");
    }

    {       // 实例代码块
        System.out.println("2");
    }

    StaticTest() {    // 实例构造器
        System.out.println("3");
        System.out.println("a=" + a + ",b=" + b);
    }

    public static void staticFunction() {   // 静态方法
        System.out.println("4");
    }

    int a = 110;    // 实例变量
    static int b = 112;     // 静态变量
}/* Output: 
        2
        3
        a=110,b=0
        1
        4
 *///:~

在初始化阶段,当JVM对类StaticTest进行初始化时,首先会执行下面的语句:

static StaticTest st = new StaticTest();

也就是实例化StaticTest对象,但这个时候类都没有初始化完毕啊,能直接进行实例化吗?事实上,这涉及到一个根本问题就是:实例初始化不一定要在类初始化结束之后才开始初始化。 下面我们结合类的加载过程说明这个问题。

我们知道,类的生命周期是:加载->验证->准备->解析->初始化->使用->卸载,并且只有在准备阶段和初始化阶段才会涉及类变量的初始化和赋值,因此我们只针对这两个阶段进行分析:

首先,在类的准备阶段需要做的是为类变量(static变量)分配内存并设置默认值(零值),因此在该阶段结束后,类变量st将变为null、b变为0。特别需要注意的是,如果类变量是final的,那么编译器在编译时就会为value生成ConstantValue属性,并在准备阶段虚拟机就会根据ConstantValue的设置将变量设置为指定的值。也就是说,如果上述程度对变量b采用如下定义方式时:

static final int b=112

那么,在准备阶段b的值就是112,而不再是0了。

此外,在类的初始化阶段需要做的是执行类构造器(),需要指出的是,类构造器本质上是编译器收集所有静态语句块和类变量的赋值语句按语句在源码中的顺序合并生成类构造器()。因此,对上述程序而言,JVM将先执行第一条静态变量的赋值语句:

st = new StaticTest ()

此时,就碰到了笔者上面的疑惑,即“在类都没有初始化完毕之前,能直接进行实例化相应的对象吗?”。事实上,从Java角度看,我们知道一个类初始化的基本常识,那就是:在同一个类加载器下,一个类型只会被初始化一次。所以,一旦开始初始化一个类型,无论是否完成,后续都不会再重新触发该类型的初始化阶段了(只考虑在同一个类加载器下的情形)。因此,在实例化上述程序中的st变量时,实际上是把实例初始化嵌入到了静态初始化流程中,并且在上面的程序中,嵌入到了静态初始化的起始位置。这就导致了实例初始化完全发生在静态初始化之前,当然,这也是导致a为110b为0的原因。
因此,上述程序的StaticTest类构造器()的实现等价于:

public class StaticTest {
    <clinit>(){
        a = 110;    // 实例变量
        System.out.println("2");        // 实例代码块
        System.out.println("3");     // 实例构造器中代码的执行
        System.out.println("a=" + a + ",b=" + b);  // 实例构造器中代码的执行
        类变量st被初始化
        System.out.println("1");        //静态代码块
        类变量b被初始化为112
    }
}

因此,上述程序会有上面的输出结果。下面,我们对上述程序稍作改动,如下所示:

public class StaticTest {
    public static void main(String[] args) {
        staticFunction();
    }

    static StaticTest st = new StaticTest();

    static {
        System.out.println("1");
    }

    {
        System.out.println("2");
    }

    StaticTest() {
        System.out.println("3");
        System.out.println("a=" + a + ",b=" + b);
    }

    public static void staticFunction() {
        System.out.println("4");
    }

    int a = 110;
    static int b = 112;
    static StaticTest st1 = new StaticTest();
}

在程序最后的一行,增加以下代码行:static StaticTest st1 = new StaticTest();
2
3
a=110,b=0
1
2
3
a=110,b=112
4

创建一个类的实例的一般初始化顺序:①②③④⑤⑥
1.类初始化
①父类–静态变量/父类–静态初始化块(谁先定义谁先执行)
②子类–静态变量/子类–静态初始化块
2.在Java对象初始化过程
主要涉及三种执行对象初始化的结构,分别是 实例变量初始化、实例代码块初始化 以及 构造函数初始化。Java要求在实例化类之前,必须先实例化其超类,以保证所创建实例的完整性。
③父类–变量/父类–初始化块(谁先定义谁先执行)
④父类–构造器
⑤子类–变量/子类–初始化块
⑥子类–构造器

子类的静态变量和静态初始化块的初始化是在父类的变量、初始化块和构造器初始化之前就完成了;
静态变量、静态初始化块顺序取决于它们在类中出现的先后顺序
变量、初始化块初始化顺序取决于它们在类中出现的先后顺序
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值