字节面试官出了一道类初始化题,我扑gai了

字节面试官出了一道类初始化题,我扑gai了

个人语录:时间不负有心人,星光不问赶路人,以顶级好的态度写一篇博客

前些天面试字节跳动,面试官都没问题什么八股文,直接问代码,直接破大防,之前自己复习的时候,把八股文理解了,但是没想到面试官问的挺深入的,最后直接扑gai了。

猫咪流泪表情包_流泪_猫咪表情

我决定自己总结一波

仰天大笑出门去,我辈岂是蓬蒿人 (倔强穷人熊猫头表情包)_蓬蒿_仰天大笑_倔强_熊猫表情

先看一下字节原题,懂得的人自然懂的,像我这种菜鸡,就ennn犹豫了半天,还是错了。

原题

public class A {
    
    
    static A a = new A();
    static {
    
    
        System.out.println("hello A");
    }
    public static void main(String[] args) {
    
    
        new A();
    }
    A() {
    
    
        System.out.println("new A");
    }
}

答案

new A
hello A
new A

具体分析见下文

别跑,回来把代码写完 - 1024,一个程序员的日子_Bug_1024_程序员_码农表情

类加载知识储备

类加载这个流程,大家都懂

image-20220414084943289

加载

把class字节码文件从各个来源通过类加载器装载入内存中

扫描二维码关注公众号,回复: 14262354 查看本文章

划重点:

  • 字节码来源:一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译
  • 类加载器:一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。
  • 为什么会有自定义类加载器?
    • 一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。
    • 另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。

验证

主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。

①文件格式验证

比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?

②元数据验证

比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?

③字节码验证

保证程序语义的合理性,比如要保证类型转换的合理性。

④符号引用验证

比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?

准备

为类变量(注意,不是实例变量)分配内存,并且赋予初值。

这里的初值:是Java虚拟机根据不同变量类型的默认初始值。

比如8种基本类型的初值,默认为0;引用类型的初值则为null;

解析

将常量池内的符号引用替换为直接引用的过程。

  • 符号引用。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。
  • 直接引用。可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量

举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。

在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。

初始化

划重点:

1、new 关键字实例化对象是调用对象的构造方法,构造方法也是静态方法的一种(《Java 编程思想》);

2、main 方法也是静态方法,所以触发类初始化的时机就是:调用类的静态对象。

类初始化

在类初始化之前的准备阶段,虚拟机会将类变量(static 修饰的变量)分配内存并设置零值。

在类初始化阶段,执行类构造器 <cinit>() 方法。<cinit> 类初始化方法有如下特点:

  • 编译器会在将 .java 文件编译成 .class 文件时,收集所有类初始化代码和 static {} 域的代码,收集在一起成为 <cinit>() 方法;
  • 子类初始化时会首先调用父类的 <cinit>() 方法;
  • JVM 会保证 <cinit>() 方法的线程安全,保证同一时间只有一个线程执行;

实例初始化

构造器:保证实例正确的初始化,能被使用。

所以,既然子类继承了父类,那么子类调用构造函数初始化的,就需要调用父类的构造器,不管是显示调用父类构造器还是 JVM 自动调用,这样才能保证子类正确的被构造。

那么,实例初始化的过程到底是如何的呢?

  • JVM 收集实例初始化变量和{} 域组合成实例初始化方法 <init>()
  • 实例初始化时首先执行 <init>() 方法,然后执行构造函数;
  • 子类通过构造函数构造实例时会首先调用父类的 <init>() 方法和父类的构造函数,如果没有显示调用父类的构造函数,那么 JVM 会自动调用父类的无参构造函数,保证父类构造函数一定被调用,然后再是子类自己的 <init>() 方法和构造函数;
  • 至此,实例就构造完毕了;

总结

  1. 父类类初始化 <cinit>()
  2. 子类类初始化 <cinit>()
  3. 父类 <init>() + 父类构造器;
  4. 子类 <init>() + 子类构造器;

例题解析

public class A {
    
    
    static A a = new A();
    static {
    
    
        System.out.println("hello A");
    }
    public static void main(String[] args) {
    
    
        new A();
    }
    A() {
    
    
        System.out.println("new A");
    }
}
解析:
    1、先执行static A a = new A();,去调用A() {
    
    
        System.out.println("new A");
    }
	2、在执行static {
    
    
        System.out.println("hello A");
    }
	3new A();调用A() {
    
    
        System.out.println("new A");
    }
结果:
new A
hello A
new A
class Window {
    
    

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

    Window (int marker) {
    
    
        System.out.println("Window(" + marker + ")");
    }
}

class House {
    
    

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

    Window w1 = new Window(1);
    House () {
    
    
        System.out.println("House()");
        w3 = new Window(33);
    }

    Window w2 = new Window(2);
    void f() {
    
    
        System.out.println("f()");
    }

    Window w3 = new Window(3);
}

public class OrderOfInitialization {
    
    
    public static void main(String[] args) {
    
    
        House h = new House();
        h.f();
    }
}

解析:
    1、执行 public static void main(String[] args) {
    
    
        House h = new House();
    }
	2、去house类里面,寻找<cinit>即执行:static {
    
    
        System.out.println("House static");
    }
	3、执行<init>Window w1 = new Window(1);
	 Window w2 = new Window(2);
	 Window w3 = new Window(3);
	4.再执行house()构造方法
    5.h.f();
结果:
House static
Window static
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()
小猪佩奇身上纹掌声送给社会人_佩奇_小猪_掌声_送给_身上表情

猜你喜欢

转载自blog.csdn.net/weixin_45882303/article/details/124164179