文章目录
- 结果详细分析
- 总结
- 作者:明志健致远
实例代码:
Parent类
public class Parent {
int a = 10;
static int b = 11;
//静态代码块
static {
System.out.println("Parent静态代码块:b=" + b);
b++;
}
//代码块
{
System.out.println("Parent代码块:a=" + a);
System.out.println("Parent代码块:b=" + b);
a++;
b++;
}
//无参构造方法
public Parent(){
System.out.println("Parent无参构造方法:a=" + a);
System.out.println("Parent无参构造方法:b=" + b);
}
//有参构造方法
public Parent(int a) {
System.out.println("Parent有参构造方法:a=" + a);
System.out.println("Parent有参构造方法:b=" + b);
}
//方法
public void function() {
System.out.println("Parent function run......");
}
}
Child类
public class Child extends Parent {
int x = 10;
static int y = 11;
//静态代码块
static {
System.out.println("Child静态代码块:y=" + y);
y++;
}
//代码块
{
System.out.println("Child代码块:x=" + x);
System.out.println("Child代码块:y=" + y);
x++;
y++;
}
//Child构造方法
public Child() {
// TODO Auto-generated constructor stub
System.out.println("Child构造方法 x=" + x);
System.out.println("Child构造方法 y=" + y);
}
public void function() {
System.out.println("Child function run......");
}
}
测试方法
public class Test {
public static void main(String[] args) {
Child demo = new Child();
demo.function();
System.out.println("…………………………………………………………………………………………………………………………");
Child child = new Child();
child.function();
}
}
输出结果
Parent静态代码块:b=11
Child静态代码块:y=11
Parent代码块: a=10
Parent代码块: b=12
Parent无参构造函数: a=11
Parent无参构造函数: b=13
Child代码块: x=10
Child代码块: y=12
Child构造函数: x=11
Child构造函数: y=13
Child function run ……
…………………………………………………………………………………………………………………………
Parent代码块: a=10
Parent代码块: b=13
Parent无参构造函数: a=11
Parent无参构造函数: b=14
Child代码块: x=10
Child代码块: y=13
Child构造函数: x=11
Child构造函数: y=14
Child function run ……
结果详细分析
运行Test类的 main 方法
- 启动JVM,开始分配内存空间;
- 加载 Test.class 文件,加载到方法区中,静态的main方法进入静态区;
- 加载完成后运行main方法,这时JVM会把main方法调用到栈中运行;
- 运行到new Child();这时JVM会在方法区中查找有没有Child文件,如果没有就加载Child.class文件,并且Child继承了Parent类,也要查找是不是有Parent文件,如果没有也要加载Parent.class文件。
- Child.class和Parent.class文件中所有的静态内容会加载到静态方法区中,非静态内容会加载到非静态的区域中。静态内容(静态变量、静态代码块、静态方法)按照书写顺序加载。
说明:类的加载只会执行一次。下次再创建对象时可以从方法区直接获取class信息。
- 开始给静态区内所有的成员变量默认初始化,默认初始化完成后,进行显示初始化。
- 所有静态成员变量显示初始化完成后,开始执行静态代码块。先执行父类的静态代码块,再执行子类的静态代码块
//这时输出
Parent静态代码块:b=11
Child静态代码块:y=11
说明:静态代码块是在类加载的时候执行的,类加载只执行一次,所以静态代码块也只执行一次;非静态代码块和构造函数是在对象创建的时候执行的,因此对象创建(new)一次,它们就执行一次。
- 这时Parent.class和Child.class加载完成。
- 开始在堆中创建Child对象。给Child对象分配内存空间,其实就是分配内存地址。
- 开始对类中的非静态成员变量进行默认初始化。
- 开始加载对应的构造方法,执行隐式三步
①有个隐式的super()
②给非静态成员变量显示初始化
③执行非静态构造代码块(无static修饰的代码块)
之后才执行构造方法
super是调用父类的构造函数,此处为Parent的构造函数,在Parent的构造函数中也有隐式三步:首先super();再执行Parent的显示初始化;执行Parent的非静态构造代码块;最后执行Parent的构造方法。
//这时输出
Parent代码块: a=10
Parent代码块: b=12
Parent无参构造函数: a=11
Parent无参构造函数: b=13
说明:虽然Parent没有明写entends,但是在Java中有一个超类Object,是所有类的父类,因此此处的super()是调用Object的构造方法。
Parent的执行完之后,回来继续执行Child自己的隐式三步的第二步:显示初始化,然后执行Child的非静态代码块;最后执行Child的构造方法。
//这时输出
Child代码块: x=10
Child代码块: y=12
Child构造函数: x=11
Child构造函数: y=13
- 对象创建完成,把内存的地址赋给demo使用
- 执行demo的function方法
//这时输出
Child function run ……
- 由于后面又创建(new)了一个新Child对象,因此从【9】之后再执行一次,输出结果为:
Parent代码块: a=10
Parent代码块: b=13
Parent无参构造函数: a=11
Parent无参构造函数: b=14
Child代码块: x=10
Child代码块: y=13
Child构造函数: x=11
Child构造函数: y=14
Child function run ……
内存运行示例图
总结
在创建(new)一个对象的时候,要先去方法区中查找该对象的类信息,如果方法区内没有,则需要先将类信息加载进来,再进行创建。
Java类被编译后,生成一个class文件,在运行时会将class文件加载到JVM中,class文件由类装载器装载,在JVM的方法区里形成一份描述class结构的元信息对象,通过该对象可获得class的结构信息:如构造函数,属性,方法等。
一、类的加载过程
- 加载阶段:
- 通过类加载的双亲委派模型将Class文件中的字节流加载到方法区中
- 在class文件加载到方法区时,先加载父类再加载子类;先加载静态内容再加载非静态内容。
- 把class中所有的静态内容加载到静态方法区下;
- 链接阶段:
- 检查加载的class内容正确性
- 静态内容加载完成后,对静态变量进行默认初始化;
- 将符号引用解析为直接引用,如果该引用没有加载,则触发该类的加载
- 初始化阶段:
- 静态变量的显示初始化,执行静态代码块(如果静态变量不是常量的话,会和静态代码块编译到一个方法中,JVM通过加锁的方式来保证该方法只被执行一次);在这个阶段之前会触发父类的加载初始化
- 类加载完成
对于静态方法和非静态方法都是被动调用,即系统不会自动执行。
二、对象的创建过程
- new一个对象时,在堆中开辟一块空间;
- 给开辟的空间分配一个地址;
- 把对象的所有非静态内容加载到所开辟的空间下;
- 非静态内容加载完成后,对非静态成员变量进行默认初始化;
- 默认初始化完成后,调用构造函数入栈执行;
- 构造函数入栈执行分为两步:!!!先执行构造函数的隐式三步,再执行构造函数内的代码!!!
①执行super()语句
②对开辟空间下的非静态成员变量进行显示初始化
③执行构造代码块
- 在整个构造函数执行完并弹栈后,把空间分配的地址赋值给引用对象。
三、其它
super语句可能出现以下三种情况:
- 构造方法体的第一句是this()语句,则不会先执行隐式三步,而是调用this()语句所对应的构造方法,最终肯定会有第一句不是this语句的构造方法。
public class Student {
private String name;
private String age;
Student() {
};
Student(String name) {
第一句不是this(),在这里先执行隐式三步,再执行this.name = name
this.name = name;
};
Student(String name, String age) {
// 不会执行隐式三步
会先执行Student(String name)构造方法
this(name);
this.age = age;
};
}
- 构造方法的第一行是super(),则调用父类的构造方法
- 构造方法体的第一行既不是this()也不是super(),则隐式调用super(),即其父类的构造方法,这也是为什么一个父类通常要提供默认构造方法的原因。
作者:明志健致远