类初始化
前提知识
类加载阶段:
- 加载 :查找该类的class字节码文件(.java文件经过编译形成的.class文件),将文件的静态存储结构转化为方法区的运行时数据结构,并利用文件创建一个java.lang.Class对象,存储在 Java 堆中,对象引用方法区的数据结构
- 验证:检查加载的 class 文件的正确性
- 准备
- 静态变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
- 如果static变量是final的基本类型以及字符串常量,编译阶段值就确定了,赋值在准备阶段完成
- 如果static变量是final的,但属于引用类型,那么赋值也会在初始化阶段完成
- 解析:将常量池中的符号引号解析为直接引用,符号引用就理解为一个标识,而在直接引用直接指向内存中的地址
- 初始化:对静态变量和静态代码块执行初始化
以上的五个阶段不是执行完上一步就要开始下一步,即加载之后就不一定就立马会初始化
区分 类的初始化 与 对象的初始化
- 类的初始化:是类加载阶段的其中一个步骤,是对静态变量、静态代码块的初始化
- 对象的初始化,一般发生于new实例对象,去调用构造方法而发生的过程
//用一个例子来说明
class User {
private int age;
private String name;
static {
System.out.println("user类初始化");
}
User(){
System.out.println("user对象无参方式初始化");
}
User(int age, String name){
this.age = age;
this.name = name;
System.out.println("user对象有参方式初始化");
}
}
public class ClassLoader {
static {
System.out.println("main方法所在类初始化");
}
public static void main(String[] args) {
System.out.println("开始执行main方法");
User user = new User();
System.out.println("第二次new一个user对象------------------------------");
User user1 = new User();
}
}
/*
运行结果:
main方法所在类初始化
开始执行main方法
user类初始化
user对象无参方式初始化
第二次new一个user对象------------------------------
user对象无参方式初始化分析:main所在类的初始化总是先发生,类初始化只发生一次
*/
类初始化发生的时机
类初始化是【懒惰的】
-
main方法所在的类,总会被首先初始化
- 相应例子对应着上方的代码
-
首次访问这个类的静态变量或静态方法时
class User {
private int age;
private String name;
static String element = "user类的静态变量";
static {
System.out.println("user类初始化");
}
User(){
System.out.println("user对象无参方式初始化");
}
User(int age, String name){
this.age = age;
this.name = name;
System.out.println("user对象有参方式初始化");
}
}
public class ClassLoader {
static {
System.out.println("main方法所在类初始化");
}
public static void main(String[] args) {
System.out.println("开始执行main方法");
//调用user类的静态变量 ,发生user类的初始化
System.out.println(User.element);
System.out.println("第二次调用类的静态变量---------");
System.out.println(User.element);
}
}
/*
运行结果:
main方法所在类初始化
开始执行main方法
user类初始化
user类的静态变量
第二次调用类的静态变量---------
user类的静态变量
分析:首次访问这个类的静态变量或静态方法时,类才会初始化,类初始化只发生一次
*/
在举例说明以下两个类初始发生时机前,先说明子类初始化的过程
class User {
private int age;
private String name;
static String element = "user类的静态变量";
{
System.out.println("user对象代码块");
}
static {
System.out.println("user类初始化");
}
User(){
System.out.println("user对象无参方式初始化");
}
User(int age, String name){
this.age = age;
this.name = name;
System.out.println("user对象有参方式初始化");
}
}
class Student extends User{
{
System.out.println("student对象代码块");
}
static {
System.out.println("student类初始化");
}
Student(){
System.out.println("student对象无参方式初始化");
}
Student(int age, String name){
System.out.println("student对象有参方式初始化");
}
}
public class ClassLoader {
static {
System.out.println("main方法所在类初始化");
}
public static void main(String[] args) {
System.out.println("开始执行main方法");
Student student = new Student();
}
}
/*
运行结果:
main方法所在类初始化
开始执行main方法
user类初始化
student类初始化
user对象代码块
user对象无参方式初始化
student对象代码块
student对象无参方式初始化
**分析:**子类初始化过程:先发生父类的初始化再发生子类的初始化,产生了父类对象,再产生子类对象
在进行对象初始化前会进行对象本身内容,对应user对象代码块,对象的初始化发生在最后
*/
- 子类初始化,如果父类还没初始化会引发父类的初始化
class User {
private int age;
private String name;
static String element = "user类的静态变量";
{
System.out.println("user对象代码块");
}
static {
System.out.println("user类初始化");
}
User(){
System.out.println("user对象无参方式初始化");
}
User(int age, String name){
this.age = age;
this.name = name;
System.out.println("user对象有参方式初始化");
}
}
class Student extends User{
{
System.out.println("student对象代码块");
}
static {
System.out.println("student类初始化");
}
Student(){
System.out.println("student对象无参方式初始化");
}
Student(int age, String name){
System.out.println("student对象有参方式初始化");
}
}
public class ClassLoader {
static {
System.out.println("main方法所在类初始化");
}
public static void main(String[] args) {
System.out.println("开始执行main方法");
System.out.println("user类-----------");
User user = new User();
System.out.println("student类-----------");
Student student = new Student();
}
}
/*
运行结果:
main方法所在类初始化
开始执行main方法
user类-----------
user类初始化
user对象代码块
user对象无参方式初始化
student类-----------
student类初始化
user对象代码块
user对象无参方式初始化
student对象代码块
student对象无参方式初始化
可以看到子类初始化,父类已经初始化了,因此user类即父类无需初始化
如果main方法里面是
System.out.println(“开始执行main方法”);
System.out.println(“student类-----------”);
Student student = new Student();
运行结果:
main方法所在类初始化
开始执行main方法
student类-----------
user类初始化
student类初始化
user对象代码块
user对象无参方式初始化
student对象代码块
student对象无参方式初始化
**分析:**子类初始化,如果父类还没初始化就会引发父类的初始化
*/
- 子类访问父类的静态变量,只会触发父类的初始化
public class ClassLoader {
static {
System.out.println("main方法所在类初始化");
}
public static void main(String[] args) {
System.out.println("开始执行main方法");
System.out.println(Student.element);
}
}
/*
运行结果:
main方法所在类初始化
开始执行main方法
user类初始化
user类的静态变量
*/
不会导致类初始化的情况
- 访问类的static final静态常量(基本类型和字符串)不会触发初始化
//将user类中的element设置为final
final static String element = "user类的静态变量";
public class ClassLoader {
static {
System.out.println("main方法所在类初始化");
}
public static void main(String[] args) {
System.out.println("开始执行main方法");
System.out.println(User.element);
}
}
/*
运行结果:
main方法所在类初始化
开始执行main方法
user类的静态变量
*/
疑问:
- 类从class文件被加载到内存时,类的数据是被存放到方法区嘛
- 类的静态变量或者静态方法存储于方法区,在从class文件中加载类,创建了一个class类对象,存储于堆,去引用类的静态数据结构
- 类的初始化之后,对象的初始化过程
- 如果类还没有被加载,则会先加载到内存
- 如果类还没有初始化,会先进行类的初始化(如果父类没有初始化,那么先进行父类的初始化)
- 对象初始化:先进行父类对象的初始化再进行子类的初始化
- 在堆内存中开辟空间,分配内存地址
- 在堆内存中建立对象的特有属性,并进行默认初始化(引用类型为null)
- 对属性进行显示初始化(如果.java文件对某些属性进行了原本赋值,例如对user类中
private String name =“java”;
- 进行构造代码块初始化
- 进行构造方法初始化
- 将内存地址赋值给栈内存中的变量