类加载过程
2019-07-23 本文已影响0人
G_uest
创建类加载过程
- 将创建对象所属类加载到方法区
如果对应类有父类,将先加载父类
修饰的静态变量随着类的加载进方法区,直接在静态区开辟了存储静态变量的内存空间(堆中)
- 在栈内存中创建对象的引用,用于存储对象的地址
- 在堆内存中开辟内存空间,给成员变量分配内存
- 给成员变量默认初始化赋值
int: 0, string: null
参考局部变量与成员变量 - 给成员变量显示初始化赋值
显示初始化时,可以把初始化语句看做是特殊的构造代码块(因为只能写声明语句),执行顺序按构造代码块位置顺序执行。所以此处5和6顺序可能会颠倒
- 执行构造代码块
构造代码块与构造方法绑定,每执行一次构造方法,执行一次构造代码块
- 给成员变量构造方法初始化赋值
- 将对象的地址,赋值给栈内存中的引用
构造代码块:在类中,方法外,用大括号括起来的一段代码,在构造方法执行之前执行。
构造代码块绑定构造方法,每执行一次类的构造方法,就会在构造方法前执行一次构造代码块。
代码验证一
inheritance.jpg这里先讲一下 继承
- 子类继承父类,new Son(),会先加载父类和子类,开辟空间,把继承自父类和自身的成员变量放进去(变量值为默认初始值,int类型为 0; String类型为 null)。
- 然后通过父类的显示初始化语句或构造方法,把父类中的变量在子类中初始化,再进入子类,把子类中的变量通过显示初始化语句或构造方法初始化
相同的方法会被重写,变量没有重写一说,如果子类声明了跟父类一样的变量,则子类中将有两个同名变量简单理解为:创建了一个子类对象时,在子类对象内存中,有两份这个变量,一份继承自父类,一份来源于子类
代码
public class Polymorphic {
public static void main(String[] args) {
Father father = new Son();
System.out.println(father);
System.out.println(father.a);
}
}
class Father {
{
System.out.println("---Base 显示初始化前--");
this.print();
}
int a = 1;
String name = "lulala";
public Father() {
this.print();
a = 2;
}
public void print() {
System.out.println("base.a = " + a);
}
}
class Son extends Father {
// 查看显示初始化之前 成员变量的值
{
System.out.println("---Son 显示初始化前---");
this.print();
}
// 显示初始化,执行顺序按照构造代码块顺序
int a = 3;
public Son() {
// super调试时为了方便看跳转位置,不写编译时会自动加上
super();
this.print();
a = 4;
}
// 重写了父类的print方法,此时父类的print方法并没有被删除和修改,只是被 隐藏
public void print() {
System.out.println("Son.a = " + a);
System.out.println("Son.name = " + name + "\n----------------");
}
}
输出结果
---Base 显示初始化前--
Son.a = 0
Son.name = null
----------------
Son.a = 0
Son.name = lulala
----------------
---Son 显示初始化前---
Son.a = 0
Son.name = lulala
----------------
Son.a = 3
Son.name = lulala
----------------
base.Son@15db9742
2
结果分析
new Son()
时会先加载父类、子类,开辟空间,给成员变量默认初始化赋值(即第1~4步)- 进入子类的构造方法(没有真正执行),遇到super()语句,跳到父类,先执行构造代码块,再执行
int a = 1;
给父类在子类空间中成员变量显示赋值,然后执行父类的构造方法,因为Son重写了父类的print()方法,所以会跳到Son中执行print()。- 执行完父类的构造方法之后,会跳到子类中开始执行构造代码块、显示赋值语句、构造方法。(因为我的构造代码块放在了显示赋值语句之前)
- 以上执行完之后,会把开辟空间的地址赋值给base,输出base的地址为base.Son@15db9742,参照print 打印对象
5. 最后输出father.a指向的是father指向内存中,Father类属性的变量a。
代码验证二
代码
import java.util.*;
public class Test {
static {
System.out.println("Test static");
}
{
System.out.println("Test 构造代码块");
}
public Test() {
}
public static void main(String[] args) {
// 只定义,不会加载类
Dog dog;
System.out.println("------创建对象,new 之前-----");
dog = new Dog("噜啦啦", 8);
System.out.println("-----dog1-----");
Dog dog1 = new Dog();
}
}
class Animal {
static {
System.out.println("Animal static");
}
public Animal (){
System.out.println("Animal 无参构造方法");
}
}
class Dog extends Animal {
private String name;
private int age = 10;
{
System.out.println("Dog 构造代码块:name = " + name + "---- age = " + age);
}
static {
System.out.println("Dog 静态代码块");
}
public Dog() {
System.out.println("Dog 无参构造方法:name = " + name + "---- age = " + age);
}
public Dog(String name, int age) {
System.out.println("Dog 有参构造方法:name = " + name + "---- age = " + age);
this.name = name;
this.age = age;
}
}
输出结果
Test static
------创建对象,new 之前-----
Animal static
Dog 静态代码块
Animal 无参构造方法
Dog 构造代码块:name = null---- age = 10
Dog 有参构造方法:name = 噜啦啦---- age = 8
-----dog1-----
Animal 无参构造方法
Dog 构造代码块:name = null---- age = 10
Dog 无参构造方法:name = null---- age = 10
结果分析
- main方法是程序的入口,所以先加载 Test 类,加载Test中的静态代码块,输出 Test static。
因为没有new Test对象,所以不会执行构造方法,自然也不会执行构造代码块
- 进入main方法,Dog dog只是定义了一个dog,并没有申请空间。不会加载类。
-
dog = new Dog("噜啦啦", 8);
会先加载 Dog 类,但是Dog继承了 Animal,则先去加载父类,然后加载Dog类 --> 成员变量默认初始化 --> 成员变量显示初始化 --> 父类的构造方法 --> Dog类的构造代码块 --> Dog类的有参构造方法。 -
Dog dog1 = new Dog();
因为Animal和Dog类已经加载过了,所以当new另一个对象的时候,不再重复加载类,直接执行构造方法。先执行父类(Animal)的构造方法,再执行子类(Dog)的构造代码块,最后是Dog类的构造方法。