通过注释,标注出下面两个类中每个方法的执行顺序,并写出studentId的最终值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package com.nezha.javase;
public class Person1 {
private int personId;
public Person1() { setId(100); }
public void setId(int id) { personId = id; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package com.nezha.javase;
public class Student1 extends Person1 {
private int studentId = 1;
public Student1() { }
@Override public void setId(int id) { super.setId(id); studentId = id; }
public void getStudentId() { System.out.println("studentId = " + studentId); } } |
1 2 3 4 5 6 7 8 9 |
package com.nezha.javase;
public class Test1 { public static void main(String[] args) { Student1 student = new Student1(); System.out.println("new Student() 完毕,开始调用getStudentId()方法"); student.getStudentId(); } } |
有兴趣的小伙伴试一下,相信我,用System.out.println标记一下每个函数执行的先后顺序,如果你全对了,下面的不用看了,大佬。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.nezha.javase;
public class Person {
private int personId;
/** * 第一步,走父类无参构造函数 */ public Person() { // 1、第一步,走父类无参构造函数 System.out.println("第一步,走父类无参构造函数"); System.out.println(""); setId(100); }
/** * 第三步,通过super.setId(id);走父类发方法 * @param id */ public void setId(int id) { System.out.println("第三步,通过super.setId(id);走父类发方法~~~id="+id); personId = id; System.out.println("在父类:studentId 被赋值为 " + personId); System.out.println(""); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
package com.nezha.javase;
public class Student extends Person {
private int studentId = 1;
/** * 在走子类无参构造函数前,会先执行子类的普通成员变量初始化 * 第五步,走子类无参构造函数 */ public Student() { System.out.println("第五步,在走子类无参构造函数前,会先执行子类的普通成员变量初始化"); System.out.println("第六步,走子类无参构造函数"); System.out.println(""); }
/** * 第二步,走子类方法 * * 走完super.setId(id);,第四步,再回此方法 * @param id */ @Override public void setId(int id) { System.out.println("第二步,走子类方法~~id="+id); // 3、第三步,走子类方法 super.setId(id); studentId = id; System.out.println("第四步,再回此方法,在子类:studentId 被赋值为 " + studentId); System.out.println(""); }
/** * 第六步,走getStudentId() */ public void getStudentId() { // 4、打印出来的值是100 System.out.println("第七步,走getStudentId()"); System.out.println("studentId = " + studentId); System.out.println(""); } } |
1 2 3 4 5 6 7 8 9 10 11 |
package com.nezha.javase;
public class Test1 { public static void main(String[] args) { Student1 student = new Student1(); System.out.println("new Student() 完毕,开始调用getStudentId()方法"); // 打印出来的值是100 System.out.println("#推测~~打印出来的值是100"); student.getStudentId(); } } |
下面通过图解JVM的方式,分析一下。
1、加载
2、链接
(1)验证(Verify)
(2)准备(Prepare)
(3)解析
3、初始化
JVM类加载器包括两种,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。
所有派生于抽象类ClassLoader的类加载器划分为自定义类加载器。
类加载器子系统负责从文件系统或网络中加载class文件,class文件在文件开头有特定的文件标识。
ClassLoader只负责class文件的加载,至于它是否可以运行,则有执行引擎决定。
加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池的信息,可能还包括字符串字面量和数字常量(这部分常量信息是class文件中常量池部分的内存映射)。
类的初始化步骤,这看似非常基础的话题,却实打实的难住了很多人,还总结了更为深入JVM的类的加载过程、类加载器的分类、类加载器的作用。