java成长笔记

类加载过程

2019-07-23  本文已影响0人  G_uest

创建类加载过程

  1. 将创建对象所属类加载到方法区
      如果对应类有父类,将先加载父类
      修饰的静态变量随着类的加载进方法区,直接在静态区开辟了存储静态变量的内存空间(堆中)
  2. 在栈内存中创建对象的引用,用于存储对象的地址
  3. 在堆内存中开辟内存空间,给成员变量分配内存
  4. 给成员变量默认初始化赋值
      int: 0, string: null 参考局部变量与成员变量
  5. 给成员变量显示初始化赋值
      显示初始化时,可以把初始化语句看做是特殊的构造代码块(因为只能写声明语句),执行顺序按构造代码块位置顺序执行。所以此处5和6顺序可能会颠倒
  6. 执行构造代码块
      构造代码块与构造方法绑定,每执行一次构造方法,执行一次构造代码块
  7. 给成员变量构造方法初始化赋值
  8. 将对象的地址,赋值给栈内存中的引用

构造代码块:在类中,方法外,用大括号括起来的一段代码,在构造方法执行之前执行。
构造代码块绑定构造方法,每执行一次类的构造方法,就会在构造方法前执行一次构造代码块。

代码验证一

这里先讲一下 继承

  1. 子类继承父类,new Son(),会先加载父类和子类,开辟空间,把继承自父类和自身的成员变量放进去(变量值为默认初始值,int类型为 0; String类型为 null)。
  2. 然后通过父类的显示初始化语句或构造方法,把父类中的变量在子类中初始化,再进入子类,把子类中的变量通过显示初始化语句或构造方法初始化
    相同的方法会被重写,变量没有重写一说,如果子类声明了跟父类一样的变量,则子类中将有两个同名变量

简单理解为:创建了一个子类对象时,在子类对象内存中,有两份这个变量,一份继承自父类,一份来源于子类

inheritance.jpg

代码

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

结果分析

  1. new Son()时会先加载父类、子类,开辟空间,给成员变量默认初始化赋值(即第1~4步)
  2. 进入子类的构造方法(没有真正执行),遇到super()语句,跳到父类,先执行构造代码块,再执行int a = 1;给父类在子类空间中成员变量显示赋值,然后执行父类的构造方法,因为Son重写了父类的print()方法,所以会跳到Son中执行print()。
  3. 执行完父类的构造方法之后,会跳到子类中开始执行构造代码块、显示赋值语句、构造方法。(因为我的构造代码块放在了显示赋值语句之前)
  4. 以上执行完之后,会把开辟空间的地址赋值给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

结果分析

  1. main方法是程序的入口,所以先加载 Test 类,加载Test中的静态代码块,输出 Test static。
      因为没有new Test对象,所以不会执行构造方法,自然也不会执行构造代码块
  2. 进入main方法,Dog dog只是定义了一个dog,并没有申请空间。不会加载类。
  3. dog = new Dog("噜啦啦", 8);会先加载 Dog 类,但是Dog继承了 Animal,则先去加载父类,然后加载Dog类 --> 成员变量默认初始化 --> 成员变量显示初始化 --> 父类的构造方法 --> Dog类的构造代码块 --> Dog类的有参构造方法。
  4. Dog dog1 = new Dog(); 因为Animal和Dog类已经加载过了,所以当new另一个对象的时候,不再重复加载类,直接执行构造方法。先执行父类(Animal)的构造方法,再执行子类(Dog)的构造代码块,最后是Dog类的构造方法。
上一篇下一篇

猜你喜欢

热点阅读