我们通过代码来分析类的加载逻辑,如有不正之处,欢迎批评指正。
代码1:
package com.ips.classloader;
public class Coordinate {
private static Coordinate obj = new Coordinate();
public static int x;
public static int y = 1;
private Coordinate() {
x++;
y++;
}
public static Coordinate getInstance() {
return obj;
}
public static void main(String[] args) {
Coordinate.getInstance();
System.out.println("x = " + x);
System.out.println("y = " + y);
}
}
执行结果:
x = 1
y = 1
代码2:
package com.ips.classloader;
public class Coordinate {
public static int x;
public static int y = 1;
private static Coordinate obj = new Coordinate();
private Coordinate() {
x++;
y++;
}
public static Coordinate getInstance() {
return obj;
}
public static void main(String[] args) {
Coordinate.getInstance();
System.out.println("x = " + x);
System.out.println("y = " + y);
}
}
执行结果:
x = 1
y = 2
分析:
代码1中成员变量声明顺序如下:
private static Coordinate obj = new Coordinate();
public static int x;
public static int y = 1;
代码2中成员变量声明顺序如下:
public static int x;
public static int y = 1;
private static Coordinate obj = new Coordinate();
我们只是对Coordinate
类的变量声明顺序做了调整,执行结果确是不一样的,为什么会这样呢,我们分析一下代码的执行逻辑。
这里涉及到类加载过程的三个阶段:装载、链接、初始化。
- 装载:就是将class文件读入内存,并对字节码的检查,看是否符合class文件规范,并在内存中生成一个代表类或接口的 java.lang.Class 实例。
- 链接:对类的基本类型变量、引用类型变量进行空间分配,同时对基本类型变量进行赋值(系统默认值)。
- 初始化:按照代码的书写顺序进行执行(这里涉及到指令重排,严格意义上代码不是按照书写顺序执行的),也就是说在初始化之前只是为类分配了存储空间,具体的变量值是系统默认值。
代码1分析:
- 装载:将
Coordinate
类加载至内存中 - 链接:为类的变量分配内存空间,分配顺序为先基本类型后引用类型,因此该类的分配顺序为x、y、obj,系统赋予的默认值为0,0,null
- 初始化:按它们声明的顺序进行初始化,先初始化obj ,执行new操作后使x、y的值分别为1,3;然后再初始化x、y,x没有指定值所以不执行保留值1,y被赋值为2,故最终结果输出为1,2。
代码2分析:
1. 装载:将Coordinate
类加载至内存中
2. 链接:为类的变量分配内存空间,分配顺序为先基本类型后引用类型,因此该类的分配顺序为x、y、obj,系统赋予的默认值为0,0,null
3. 初始化:按它们声明的顺序进行初始化,先初始化x、y,x没有指定值所以不进行赋值,保留系统默认值,y被赋值为2,之后初始化obj,执行new操作后,x、y的值分别为1、3,故最终结果输出为1,3。
若我们将x、y修改为实例变量(非静态变量),执行结果肯定是1、3,两个类中的非静态变量值是互不影响的。