相信我们在面试 Java 的时候总会有一些公司要做笔试题目的,而 Java 类的加载和对象创建流程的知识点也是常见的题目之一。接下来通过实例详细的分析一下。
实例代码
- package mytest.javaBase;
- 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);
- b++;
- a++;
- }
- // 无参构造函数
- Parent() {
- System.out.println("Parent无参构造函数: a=" + a);
- System.out.println("Parent无参构造函数: b=" + b);
- }
- // 有参构造函数
- Parent(int a) {
- System.out.println("Parent有参构造函数: a=" + a);
- System.out.println("Parent有参构造函数: b=" + b);
- }
- // 方法
- void
- function() {
- System.out.println("Parent function run ……");
- }
- }
- package mytest.javaBase;
- 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);
- y++;
- x++;
- }
- // 构造函数
- Child() {
- System.out.println("Child构造函数: x=" + x);
- System.out.println("Child构造函数: y=" + y);
- }
- // 方法
- void
- function() {
- System.out.println("Child function run ……");
- }
- }
- package mytest.javaBase;
- 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 方法
1、 启动 JVM,开始分配内存空间;
2、 开始加载 Test.class 文件,加载到方法区中,在加载的过程中静态的内容要进入静态区中;
3、 在开始运行 main 方法,这时 JVM 就会把 main 调用到栈中运行,开始从方法的第一行往下运行;
4、 在 main 方法中 new Child();这时 JVM 就会在方法区中查找有没有 Child 文件,如果没有就加载 Child.class 文件,并且 Child 继承 Parent 类,所以也要查找有没有 Parent 类,如果没有也要加载 Parent.class 文件。
5、 Child.class 和 Parent.class 中的所有的非静态内容会加载到非静态的区域中,而静态的内容会加载到静态区中。在加载静态的过程中,先要加载静态的成员变量,然后再加载静态代码块,之后再加载静态的成员方法。
说明:类的加载只会执行一次。下次再创建对象时,可以直接在方法区中获取 class 信息。
6、 开始给静态区中的所有静态的成员变量开始默认初始化。默认初始化完成之后,开始给所有的静态成员变量显示初始化。
7、 所有静态成员变量显示初始化完成之后,开始执行静态的代码块。先执行父类的静态代码块,再执行子类的静态代码块。
- //这时输出
- Parent静态代码块:b=11
- Child静态代码块:y=11
说明:>> 静态代码块是在类加载的时候执行的,类的加载只会执行一次所以静态代码块也只会执行一次;
>>非静态代码块和构造函数中的代码是在对象创建的时候执行的,因此对象创建 (new) 一次,它们就会执行一次。
8、 这时 Parent.class 文件 和 Child.class 文件加载完成。
9、 开始在堆中创建 Child 对象。给 Child 对象分配内存空间,其实就是分配内存地址。
10、开始对类中的的非静态的成员变量开始默认初始化。
11、开始加载对应的构造方法,执行隐式三步
- ①有个隐式的super();
- ②显示初始化(给所有的非静态的成员变量)
- ③执行构造代码块
- 之后才开始执行本类的构造方法中的代码
super() 是调用父类的构造函数,此处即为 Parent 的构造函数,在 Parent 的构造函数中也有个隐式三步:首先 super(),再执行 Parent 的显示初始化,然后执行 Parent 的非静态构造代码块,最后执行 Parent 的构造函数中的代码。
- //这时输出
- Parent代码块: a=10
- Parent代码块: b=12
- Parent无参构造函数: a=11
- Parent无参构造函数: b=13
说明:虽然 Parent 没有明写 extends,但是我们知道在 Java 中有个超类 Object,它是所有类的父类,因此此处 Parent 类的 super() 是调用 Object 的构造函数
Parent 的执行完之后,回来继续执行 Child 自己的隐式三步中的第二步:显示初始化,然后执行 Child 的非静态代码块的,最后执行 Child 的构造函数中的代码
- //这时输出
- Child代码块: x=10
- Child代码块: y=12
- Child构造函数: x=11
- Child构造函数: y=13
12、对象创建完成,把内存的地址赋值给 demo 使用。
13、执行 demo.function() 方法。
- //这时输出
- Child function run ……
14、由于后面又创建 (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) 一个对象的时候,先要去 JVM 的方法区里获取该对象所对应的类的信息,如果方法区里没有该类的信息,则需要去将它加载进来,加载进来之后,有了该类的信息,我们才能创建一个对象。
一般,Java 类被编译后,会生成一个 class 文件,在运行的时候会将 class 文件加载到 Java 虚拟机 JVM 中,class 文件由类装载器装载,在 JVM 中(准确的来说应该是在 JVM 的方法区里)将形成一份描述 Class 结构的元信息对象,通过该元信息对象可以获知 Class 的结构信息:如构造函数,属性和方法等。
首先,Jvm 在执行时,遇到一个新的类时,会到内存中的方法区去找 class 的信息,如果找到就直接拿来用,如果没有找到,就会去将类文件加载到方法区。在类加载时,静态成员变量加载到方法区的静态区域,非静态成员变量加载到方法区的非静态区域。
静态代码块是在类加载时自动执行的代码,非静态代码块是在创建对象时自动执行的代码,不创建对象不执行该类的非静态代码块。
顺序: 静态代码块 --》非静态代码块 --》类构造方法。
加载过程:
1、JVM 会先去方法区中找有没有相应类的. class 存在。如果有,就直接使用;如果没有,则把相关类的. clss 加载到方法区。
2、在. class 加载到方法区时,先加载父类再加载子类;先加载静态内容,再加载非静态内容
3、加载静态内容:
4、加载非静态内容:把. class 中的所有非静态变量及非静态代码块加载到方法区下的非静态区域内。
5、执行完之后,整个类的加载就完成了。
对于静态方法和非静态方法都是被动调用,即系统不会自动调用执行,所以用户没有调用时都不执行,主要区别在于静态方法可以直接用类名直接调用(实例化对象也可以),而非静态方法只能先实例化对象后才能调用。
1、new 一个对象时,在堆内存中开辟一块空间。
2、给开辟的空间分配一个地址。
3、把对象的所有非静态成员加载到所开辟的空间下。
4、所有的非静态成员加载完成之后,对所有非静态成员变量进行默认初始化。
5、所有非静态成员变量默认初始化完成之后,调用构造函数。
6、在构造函数入栈执行时,分为两部分:先执行构造函数中的隐式三步,再执行构造函数中书写的代码。
|
7、在整个构造函数执行完并弹栈后,把空间分配的地址赋给引用对象。
1)构造方法体的第一行是 this 语句,则不会执行隐式三步,而是调用 this 语句所对应的的构造方法,最终肯定会有第一行不是 this 语句的构造方法。
- package mytest.javaBase;
- public class Student {
- private String name;
- private String age;
- Student() {
- };
- Student(String name) {
- this.name = name;
- };
- Student(String name, String age) {
- // 不会执行隐式三步
- this(name);
- this.age = age;
- };
- }
2)构造方法体的第一行是 super 语句,则调用相应的父类的构造方法, 3)构造方法体的第一行既不是 this 语句也不是 super 语句,则隐式调用 super(),即其父类的默认构造方法,这也是为什么一个父类通常要提供默认构造方法的原因;
更多内容:http://www.cnblogs.com/study-everyday/
来源: http://www.cnblogs.com/study-everyday/p/6752715.html