写这篇文章的目的:在阅读 Java 编程思想时候发现,自己对 Java 的类加载和对象创建的印象有些模糊了,特别是当遇到多态和继承的时候,特意写这篇文章来加强下自己的知识点。这篇文章只是自己对类加载的简单理解, 有关 JVM 在类加载过程中的细节不在本文的讨论范围内。
1. 类加载的过程
JVM 会先去方法区中找有没有对应的 class 文件存在,如果有直接使用,如果没有则将对应的 class 文件加载到方法区内。类加载的过程中,如果发现当前类还有父类,那么逐级向上,先加载顶层的父类,然后在加载下面的子类。
将 class 文件中的静态变量加载到方法区下的静态区域内,将非静态变量加载到方法区下的非静态区域内。
对所有静态变量进行默认初始化。
再对静态变量进行显示初始化。
执行静态代码块。
整个类加载完成。
代码
public class InitClass {
private static int j = 5;
private static int i = j++;
static {
System.out.println("static invoked, i = " + i + ", j = " + j);
}
public static void main(String[] args) {
new InitClass();
}
}
结果:
static invoked, i = 5, j = 6
2. 对象创建的过程
在堆内存中开辟一块空间
给空间分配一个地址
把对象所有的非静态变量加载到所开辟的空间下。
对所有非静态变量进行默认初始化
调用构造函数
在构造函数入栈时分为两步,先执行隐式三步,再执行构造函数中书写的代码
- 隐式三步为:
- 执行 super 语句
- 对所有非静态变量进行显示初始化
- 执行构造代码块
- 隐式三步为:
把空间分配的地址赋值给一个引用对象。
注意的细节: 当有继承关系的情景的时候,首先创建父类对象,然后在创建子类对象。
代码验证:
class SuperClasses {
private int k = 9;
public SuperClasses() {
System.out.println("k = " + k);
System.out.println("父类构造器执行了!");
}
}
public class InitClass extends SuperClasses {
private int i = 5;
private int j;
{
System.out.println("i = " + i + ", j = " + j++);
System.out.println("构造代码块执行了!");
}
public InitClass() {
System.out.println("i = " + i + ", j = " + j);
System.out.println("构造函数执行了!");
}
public static void main(String[] args) {
new InitClass();
}
}
结果:
k = 9
父类构造器执行了!
i = 5, j = 0
构造代码块执行了!
i = 5, j = 1
构造函数执行了!
3. 多态中构造函数内部的行为
如果一个类的构造函数内部调用了一个正在构造的对象的某个动态绑定,那么会发生什么情况呢?先来看代码:
class Father {
public void show() {
System.out.println("Father.show()");
}
public Father() {
System.out.println("Father before show()");
show();
System.out.println("Father after show()");
}
}
class Son extends Father {
private int money = 100;
public Son(int money) {
this.money = money;
System.out.println("Son constructor, money = " + money);
}
@Override
public void show() {
System.out.println("Son.show(), money = " + money);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new Son(88);
}
}
结果:
Father before show()
Son.show(), money = 0
Father after show()
Son constructor, money = 88
显然这样的调用结构是有问题的,此时的子类实例变量并没有初始化,这种方式是错误的,要提高警惕。