- 引入阿里巴巴的一道面试题
public class Test1 {
public static int k = 0;
public static Test1 t1 = new Test1("t1");
public static Test1 t2 = new Test1("t2");
public static int i = print("i");//3
public static int n = 99;
public int j = print("j");
static {
print("静态块");
}
public Test1(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++i;
++n;
}
{
print("构造块");
}
public static int print(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++n;
return ++i;
}
public static void main(String[] args) {
Test1 t = new Test1("init");
}
}
参考答案
1:j i=0 n=0
2:构造块 i=1 n=1
3:t1 i=2 n=2
4:j i=3 n=3
5:构造块 i=4 n=4
6:t2 i=5 n=5
7:i i=6 n=6
8:静态块 i=7 n=99
9:j i=8 n=100
10:构造块 i=9 n=101
11:init i=10 n=102
是不是一脸懵逼,无从下手,下面开始了解涉及的知识
类加载及初始化
-
一、类加载机制
-
1、加载 :根据二进制源文件的字节流,创建类或接口的所对应的Class对象的过程
步骤- 通过一个类的全限定名获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在Java堆中生成一个代表这个类的java.lang.Class对象, 作为对方法区中这些数据的访问入口
-
2、链接
-
验证:保证被加载类的正确性
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
-
准备 : 为类的静态变量分配内存, 并将其初始化为默认值
class A{ static int a=10; } 准备阶段会为a静态变量分配4个字节的空间,并初始值为0。
-
解析
- 把类中的符号引用转换为直接引用
- 例如:调用一个方法时,在java语言层面就是一个方法名这个符号,但是在底层应该要根据这个符号找到内存中的直接地址来调用
-
-
3、初始化 :对类的静态变量, 静态代码块执行初始化操作
class A{ static int a = 10; } 到了初始化阶段,a的值就可以赋值为10了
-
-
二、类初始化过程【重点】
-
一个类要实例化,必须先要让自己先初始化,类初始化过程主要是对静态成分的初始化
-
1、方法详解
当一个类编译之后,字节码文件中会产生一个类构造器方法==:<cinit>(),而这个方法中的逻辑就是当前类中的静态变量和静态代码块的初始化逻辑整合
-
2、静态变量和静态代码块初始化顺序
- 静态变量 : 在准备阶段分配内存空间,默认初始值 ; 在类初始化过程,才正式赋值
- 静态代码块: 在类初始化过程执行
- 静态代码块和静态变量直接赋值初始化顺序是按照编码顺序从上到下完成的。静态变量的值以最后一次赋值为准
-
三、继承中类初始化分析
- 当一个类存在父类时,一定是先对父类进行初始化然后再初始化子类的
-
对象创建和初始化过程
- 类如果没有初始化,先初始化
- 整个流程
- 当调用一个new指令,虚拟机先看类有没有初始化(没有就先初始化)
- 初始化完后,虚拟机给实例对象分配一个内存空间(对象所需内存的大小在类加载完成后便可完全确定)
- 对象内存分配同时,实例变量也会被赋予默认值(零值)
- Java对象初始化过程中
- 实例变量初始化
- 实例代码块初始化
- 构造函数初始化