概要
这一章的复习主要涵盖 类的初始化触发条件,类初始化的顺序,构造器的性质特点,成员初始化,以及 数组初始化。这里整理的都是个人觉得比较重要重新认真分析的东西,不一定全部为基础,如果有不准确的地方,还望大佬从评论区指出,感激不尽。
文章中的所有的自定义的测试都在自己的 java-review
仓库中,本文对应的链接:
类的初始化
类初始化的触发
当出现调用的时候,就会触发类加载
4
条字节码指令
字节码指令 | 对应的情况 |
---|---|
new | 使用 new 关键字实例化对象 |
getstatic | 读取一个类的静态字段 (被 final 修饰、已在编译期把结果放入常量池的静态字段除外) |
putstatic | 设置一个类的静态字段 (同上) |
invokestatic | 调用一个类的静态方法 |
java.lang.reflect 包的方法对类进行 反射调用 的 还没有进行初始化的类
初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要 先触发其父类的初始化
用户需要指定一个要执行的主类(包含
main()
方法的那个类),虚拟机会先初始化这个主类JDK 1.7
的动态语言支持时,如果一个java.lang.invoke.MethodHandle
实例最后的解析结果REF_getStatic
、REF_putStatic
、REF_invokeStatic
的方法句柄,并且这个方法句柄所对应的类没有进行过初始化
被动引用
被动引用不会触发类的初始化
调用子类的中继承自父类的静态属性,只触发父类的类加载,不会触发子类的类加载,只触发拥有该属性的类型的初始化
测试通过创建指定类型的数组,不会触发类的初始化
测试调用类的/类继承的
static final
属性static final
修饰的为基本数据类型的时候,在 准备阶段 中就将这个常量进行了赋值并放倒了 类的常量池 中,不会触发静态成员初始化static final
成员变量使用调用static
方法进行赋值的时候,不会在准备阶段完成初始化,只有在类加载的静态变量/域初始化的时候后才会完成初始化
目录结构
1 | passivereference |
1 | // test |
初始化顺序
- 第一次 调用一个类的 构造器 或者 静态属性/方法 的时候就类加载器会加载这个类,完成 静态属性/域的初始化
- 父类 -> 子类
- 按照静态属性/域 声明顺序
new
关键为对象分配空间,此时所有的成员变量都被设置成了默认值(默认的零值)- 引用类型默认值:
null
- 基本类型的默认值详见: 基本类型的零值
- 引用类型默认值:
- 非静态成员属性初始化
- 父类 -> 子类
- 调用构造器
- 父类 -> 子类
初始化顺序的验证
1 | // java test |
InitOrder
拥有main
方法,触发了类加载器初始化了
t
的静态成员变量k
调用了
t1 = new InitOrder("obj 1-> constructor");
,初始化t
1 静态成员变量- 初始化
t1
的 非静态成员变量/域 - 调用构造器,完成了
t1
的初始化
- 初始化
调用
t2 = new InitOrder("obj 2-> constructor");
,初始化t2
静态成员变量- 初始化了
t2
的非静态成员变量 - 调用构造器,完成了
t2
的初始化
(这两个静态对象引用初始化的时候,都忽略了剩余的静态属性,直接初始化非静态成员变量)
- 初始化了
初始化
t
的静态块初始化
t
的非静态成员变量/块调用
t
的构造器,完成初始化
静态属性/域
一个类的所有的对象都公用 static
静态属性
构造器
构造器的性质
- 使用
new
关键字的时候就会为对象分配空间,并调用指定的构造器,构造器只用于完成初始化 - 构造器不是
static
方法,具体证明在后面的特使代码中
字节码指令 | 用途 |
---|---|
invokestatic |
用于调用类(静态)方法 |
invokespecial |
用于调用实例方法,特化于 super 方法调用、private 方法调用与构造器调用 |
invokevirtual |
用于调用一般实例方法(包括声明为 final 但不为 private 的实例方法) |
invokeinterface |
用于调用接口方法 |
1 | // test |
从反编译的代码中可以看出,调用构造器和实例方法所用的字节码指令相同,所以可以在调用构造器的时候,其实已经完成了对象实例的空间分配,即已经存在了这个对象了。
调用构造器方法的作用 为了将对象实例进行初始化
默认(无参) 构造器
- 类中没有声明任何构造器的时候,编译器会自动添加一个构造器,即
public ClassName() {}
不带有任何参数的 默认构造器,也叫 无参构造器 - 类中声明了构造器时,编译器就不会添加默认构造器,此时再调用默认构造器的是时候就会 报错
方法重载
构造器本身就是一种特殊的方法,因为同样支持重载,详细的方法重载会放到后面的基本对象方法中 这里先留一个坑
java
中支持方法的重载,区分重载的依据
- 参数类型
- 参数个数
- 参数顺序
成员的初始化
类的成员变量
在对象空间分配的过程中,给类的所有的成员变量赋以默认的零值(这里同样请参看数据类型),然后才开始进行类的初始化过程
局部变量
编译器不会对局部变量赋予默认值,因此在使用前 必须进行初始化,否则就会编译出错
指定初始化
初始化的顺序为自上而下的声明顺序,在开始非静态成员的初始化的时候,类对象其实已经分配完成,此时就可以调用的类的非静态方法
###构造器初始化
一般也经常自写一个 init()
初始化方法在构造器中进行调用,用来初始化指定的成员变量
静态数据的初始化
static
关键字只能用于域,不能用于局部变量
数组的初始化
数组类本身不是通过类加载器创建的,它是由
java
虚拟机 直接创建的
编译器不允许指定数组的大小 ,这就刚好印证了 int[12] arr;
java
是一种面向对象的语言,arr
实际上是指向一个数组对象的 引用
基本数据类型和数组的关系
- 类似于
int[]
的基本数据类型数组同样是一个对象,可以使用new
关键字来声明 - 单个的基本数据类型不能这样声明
数组类创建规则
name | 类型名 | 含义 |
---|---|---|
Element Type | 元素类型 | 去掉所有维度后的类型 |
Element Type | 组件类型 | 去掉一个维度的类型 |
元素类型最终需要 类加载器 创建
一个数组类(下面简称为 c
)创建过程就遵循以下规则
组件类型为 引用类型,就递归地去加载这个组件类型,数组
C
将在加载该组件类型的类加载器的类名称空间上被标识组件类型为 基本数据类型,
Java
虚拟机将会把数组C
标记为与引导类加载器关联。数组类的可见性与它的组件类型的可见性一致,如果组件类型不是引用类型,那数组类的可见性将默认为
public
可变参数类型
参编参数列表可以接受 一个数组类型 ,或者是 多个指定类型
java
的自动装箱机制对数组类型是不起作用的,使用可变参数列表,可以将传入的基本类型都进行自动装箱/拆箱操作
1 | // test |
枚举类型
- 枚举类不能继承
- 每个枚举具名值都是一个该枚举类的实例对象
- 支持
switch
1 | // test |