一网打尽“类”的初始化实例化知识点

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 之前说了类加载的过程,但是有的读者表示还是有些面试题还是答不来,所以今天就来总结下类加载、对象实例化方面的知识点/面试题,帮助大家加深印象。

前言


之前说了类加载的过程,但是有的读者表示还是有些面试题还是答不来,所以今天就来总结下类加载、对象实例化方面的知识点/面试题,帮助大家加深印象。


全是干货,一网打尽类的基础知识!先看看下面的问题都能回答上来吗?


  • 描述new一个对象的过程,并结合例子说明。
  • 类初始化的触发时机。
  • 多线程进行类的初始化会出问题吗?
  • 类的实例化触发时机。
  • <clinit>()方法和<init>()方法区别。
  • 在类都没有初始化完毕之前,能直接进行实例化相应的对象吗?
  • 类的初始化过程与类的实例化过程的异同?
  • 一个实例变量在对象初始化的过程中会被赋值几次?


描述new一个对象的过程


先上图,再描述:


0.png


1.png


2.png


Java中对象的创建过程包括 类初始化和类实例化两个阶段。而new就是创建对象的一种方式,一种时机。


当执行到new的字节码指令的时候,会先判断这个类是否已经初始化,如果没有初始化就要进行类的初始化,也就是执行类构造器<clinit>()方法。如果已经初始化了,就直接进行类对象的实例化。


  • 类的初始化,是类的生命周期中的一个阶段,会为类中各个类成员赋初始值。
  • 类的实例化,是指创建一个类的实例的过程。


但是在类的初始化之前,JVM会保证类的装载,链接(验证、准备、解析)四个阶段都已经完成,也就是上面的第一张图。


  • 装载是指 Java虚拟机查找.class文件并生成字节流,然后根据字节流创建java.lang.Class对象的过程。
  • 链接是指验证创建的类,并将其解析到JVM中使之能够被 JVM 执行。


那到底类加载的时机是什么时候呢?JVM 并没有规范何时具体执行,不同虚拟机的实现会有不同,常见有以下两种情况:


  • 隐式装载:在程序运行过程中,当碰到通过 new 等方式生成对象时,系统会隐式调用 ClassLoader 去装载对应的 class 到内存中;
  • 显示装载:在编写源代码时,主动调用 Class.forName() 等方法也会进行 class 装载操作,这种方式通常称为显示装载。


所以到这里,大的流程框架就搞清楚了:


  • JVM碰到new字节码的时候,会先判断类是否已经初始化,如果没有初始化(有可能类还没有加载,如果是隐式装载,此时应该还没有类加载,就会先进行装载、验证、准备、解析四个阶段),然后进行类初始化
  • 如果已经初始化过了,就直接开始类对象的实例化工作,这时候会调用类对象的<init>方法。


结合例子说明


然后说说具体的逻辑,结合一段类代码:


public class Run {
    public static void main(String[] args) {
        new Student();
    }
}
public class Person{
    public static int value1 = 100;
    public static final int value2 = 200;
    public int value4 = 400;
    static{
        value1 = 101;
        System.out.println("1");
    }
    {
        value1 = 102;
        System.out.println("3");
    }
    public Person(){
        value1 = 103;
        System.out.println("4");
    }
}
public class Student extends Person{
    public static int value3 = 300;
    public int value5 = 500;
    static{
        value3 = 301;
        System.out.println("2");
    }
    {
        value3 = 302;
        System.out.println("5");
    }
    public Student(){
        value3 = 303;
        System.out.println("6");
    }
}


  • 首先是类装载,链接(验证、准备、解析)。


  • 当执行类准备过程中,会对类中的静态变量分配内存,并设置为初始值也就是“0值”。比如上述代码中的value1,value3,会为他们分配内存,并将其设置为0。但是注意,用final修饰静态常量value2,会在这一步就设置好初始值102。


  • 初始化阶段,会执行类构造器<clinit>方法,其主要工作就是初始化类中静态的(变量,代码块)。但是在当前类的<clinit>方法执行之前,会保证其父类的<clinit>方法已经执行完毕,所以一开始会执行最上面的父类Object的<clinit>方法,这个例子中会先初始化父类Person,再初始化子类Student。


  • 初始化中,静态变量和静态代码块顺序是由语句在源文件中出现的顺序所决定的,也就是谁写在前面就先执行谁。所以这里先执行父类中的value1=100,value1 = 101,然后执行子类中的value3 = 300,value3 = 301


  • 接着就是创建对象的过程,也就是类的实例化,当对象被类创建时,虚拟机会分配内存来存放对象自己的实例变量和父类继承过来的实例变量,同时会为这些事例变量赋予默认值(0值)。


  • 分配完内存后,会初始化父类的普通成员变量(value4 = 400),和执行父类的普通代码块(value1=102),顺序由代码顺序决定。


  • 执行父类的构造函数(value1 = 103)


  • 父类实例化完了,就实例化子类,初始化子类的普通成员变量(value5 = 500),执行子类的普通代码块(value3 = 302),顺序由代码顺序决定。
  • 执行子类的构造函数(value3 = 303)


所以上述例子打印的结果是:


123456


总结一下执行流程就是:


  1. 父类静态变量和静态代码块;
  2. 子类静态变量和静态代码块;
  3. 父类普通成员变量和普通代码块;
  4. 父类的构造函数;
  5. 子类普通成员变量和普通代码块;
  6. 子类的构造函数。


最后,大家再结合流程图好好梳理一下:


0.png


1.png


2.png


类初始化的触发时机


在同一个类加载器下,一个类型只会被初始化一次,刚才说到new对象是类初始化的一个判断时机,其实一共有六种能够触发类初始化的时机:


  • 虚拟机启动时,初始化包含 main 方法的主类;
  • 遇到 new等指令创建对象实例时,如果目标对象类没有被初始化则进行初始化操作;
  • 当遇到访问静态方法或者静态字段的指令时,如果目标对象类没有被初始化则进行初始化操作;
  • 子类的初始化过程如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化;
  • 使用反射API 进行反射调用时,如果类没有进行过初始化则需要先触发其初始化;
  • 第一次调用java.lang.invoke.MethodHandle 实例时,需要初始化 MethodHandle 指向方法所在的类。


多线程进行类的初始化会出问题吗


不会,<clinit>()方法是阻塞的,在多线程环境下,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>(),其他线程都会被阻塞。


类的实例化触发时机


  • 使用new关键字创建对象
  • 使用Class类的newInstance方法,Constructor类的newInstance方法(反射机制)
  • 使用Clone方法创建对象
  • 使用(反)序列化机制创建对象


<clinit>()方法和<init>()方法区别。


  • <clinit>()方法发生在类初始化阶段,会执行类中的静态类变量的初始化和静态代码块中的逻辑,执行顺序就是语句在源文件中出现的顺序。


  • <init>()方法发生在类实例化阶段,是默认的构造函数,会执行普通成员变量的初始化和普通代码块的逻辑,执行顺序就是语句在源文件中出现的顺序。


在类都没有初始化完毕之前,能直接进行实例化相应的对象吗?


刚才都说了先初始化,再实例化,如果这个问题可以的话那不是打脸了吗?


没错,要打脸了哈哈。


确实是先进行类的初始化,再进行类的实例化,但是如果我们在类的初始化阶段就直接实例化对象呢?比如:


public class Run {
    public static void main(String[] args) {
        new Person2();
    }
}
public class Person2 {
    public static int value1 = 100;
    public static final int value2 = 200;
    public static Person2 p = new Person2();
    public int value4 = 400;
    static{
        value1 = 101;
        System.out.println("1");
    }
    {
        value1 = 102;
        System.out.println("2");
    }
    public Person2(){
        value1 = 103;
        System.out.println("3");
    }
}


嘿嘿,这时候该怎么打印结果呢?


按照上面说过的逻辑,应该是先静态变量和静态代码块,然后普通成员变量和普通代码块,最后是构造函数。


但是因为静态变量又执行了一次new Person2(),所以实例化过程被强行提前了,在初始化过程中就进行了实例化。这段代码的结果就变成了:


23123


所以,实例化不一定要在类初始化结束之后才开始初始化,有可能在初始化过程中就进行了实例化。


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


学了上面的内容,这个问题就很简单了:


  • 类的初始化,是指在类装载,链接之后的一个阶段,会执行<clinit>()方法,初始化静态变量,执行静态代码块等。


  • 类的实例化,是指在类完全加载到内存中后创建对象的过程,会执行<init>()方法,初始化普通变量,调用普通代码块。


一个实例变量在对象初始化的过程中最多可以被赋值几次?


那我们就试试举例出最多的情况,其实也就是每个要经过的地方都对实例变量进行一次赋值:


  • 1、对象被创建时候,分配内存会把实例变量赋予默认值,这是肯定会发生的。
  • 2、实例变量本身初始化的时候,就给他赋值一次,也就是int value1=100。
  • 3、初始化代码块的时候,也赋值一次。
  • 4、构造函数中,在进行赋值一次。


一共四次,看代码:


public class Person3 {
    public int value1 = 100;
    {
        value1 = 102;
        System.out.println("2");
    }
    public Person3(){
        value1 = 103;
        System.out.println("3");
    }
}


参考


https://blog.csdn.net/justloveyou_/article/details/72466416


https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc?id=1860


https://www.jianshu.com/p/8a14ed0ed1e9


目录
相关文章
|
8月前
|
Java 编译器
JavaSE——面向对象基础(2/4)-this关键字、构造器(this的执行原理、应用场景,构造器的特点、应用)
JavaSE——面向对象基础(2/4)-this关键字、构造器(this的执行原理、应用场景,构造器的特点、应用)
47 5
|
9月前
|
Python
python面型对象编程进阶(继承、多态、私有化、异常捕获、类属性和类方法)(上)
python面型对象编程进阶(继承、多态、私有化、异常捕获、类属性和类方法)(上)
97 0
|
9月前
|
Java
Java面向对象编程,构造函数和方法的区别是什么?
Java面向对象编程,构造函数和方法的区别是什么?
148 2
C++类的学习1(二)
的定义一般包括两部分,一是类的属性,二是他所拥有的方法。类的实例化是指给类的加载并初始化过程,比如一个people类,我们具体到每一个人就是类的实例化,此外一个类可以在此类上进行扩展。比如people类,我们分为 外国people 和 中国people,那么people叫做基类,外国people叫做派生类或子类
111 0
C++类的学习1(一)
的定义一般包括两部分,一是类的属性,二是他所拥有的方法。类的实例化是指给类的加载并初始化过程,比如一个people类,我们具体到每一个人就是类的实例化,此外一个类可以在此类上进行扩展。比如people类,我们分为 外国people 和 中国people,那么people叫做基类,外国people叫做派生类或子类
|
安全 Java
创建对象的相关知识补充
创建对象的相关知识补充
75 0
|
前端开发
前端学习案例5-构造函数继承1
前端学习案例5-构造函数继承1
92 0
前端学习案例5-构造函数继承1
对象和类的知识点总结
对象和类的知识点总结
96 0
|
PHP 开发者
类的加载(概念和步骤)|学习笔记
快速学习类的加载(概念和步骤)