引言
在日常工作中,比较少的机会会探究类加载机制,类加载顺序,但是这部分在代码优化,深入学习jvm有着极大的帮助,本文帮助不太了解类加载顺序的人从代码层级了解类加载顺序。
示例1-普通类
class Foo{
private int x;
{
System.out.println("1");
}
static {
System.out.println("2");
}
public Foo() {
System.out.println("3");
System.out.println("x=" + x + ",z=" + z);
}
public Foo(int x) {
super();
System.out.println("4");
this.x = x;
}
static int z = 8;
public void say() {
System.out.println("5");
}
public static void say2() {
{
System.out.println("6");
}
System.out.println("7");
}
}
public class App {
public static void main(String[] args) {
Foo.say2();
}
}
很多人拿到这样的代码就会打颤,别着急,接着向下看,如果你耐心读完此文,相信再遇到此类的问题,乃至面试题都会如鱼得水,整理的头头是道!
java类中包含的成员
首先,我们了解一下java类中包含的成员有哪些
成员 | 解释 |
---|---|
成员变量 | 类的直属变量 |
方法 | 类的直属方法 |
局部变量 | 方法体中定义的变量和方法中涉及的变量 |
代码块 | 方法体中定义的代码块 |
构造代码块 | 类的直属方法跨 |
静态块 | 类的直属方法块 |
静态方法 | static修饰的方法 |
静态成员变量 | static 修饰的类的直属变量 |
成员内部类 | 在一个类内部进行其他类结构的嵌套操作 |
局部内部类 | 定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内 |
匿名内部类 | 平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护,例如子线程的runnable对象 |
静态内部类 | 定义在了成员位置上,并且使用static来去修饰的类 |
类实例化顺序
了解了类的成员信息后,下面讲下类实例化顺序
- 执行静态块
- 执行构造代码块(构造代码块一定在构造器前执行)
- 执行构造器
了解了这些之后,可能有些细心地童鞋会有疑问,还有静态成员变量呢?普通成员变量呢?他们什么时候执行?
我们可以把静态成员变量看做静态块,普通成员变量看做构造代码块来处理,这样是不是简单很多。
类加载的顺序
- 调用构造器
- class.forname
- 调静态字段
- 调静态方法
public class AppTest {
public static void main(String[] args) {
f1();
}
static AppTest t = new AppTest();
static {
System.out.println("1");
}
{
System.out.println("2");
}
AppTest() {
System.out.println("3");
System.out.println("a=" + a + ",b=" + b);
}
public static void f1() {
System.out.println("4");
}
int a = 110;
static int b = 112;
}
类加载的时机
类从被加载到虚拟机内存中开始,到卸载出内存为止,
内部类
(1)、内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号 。
(2)、内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是private的 。
(3)、内部类声明成静态的,就不能随便的访问外部类的成员变量了,此时内部类只能访问外部类的静态成员变量 。
子类C继承父类F
当调用子类静态方法时,执行顺序为
- 按照继承顺序从顶级父类开始加载类
- 执行子类C中的静态块
- 执行子类静态块
- 执行调用方法
当调用子类非静态方法
- 初始化(加载)所有超类
- 加载自身静态块
- 再次初始化所有超类及自身
- 执行调用方法
解释
- 按照继承顺序从顶级父类开始所加载类(按照顶级类到子类开始把相关类加载进内存中)
- 因为使用了new,所以会创建一个全新的子类对象,所以之前的超类由于连带关系均需要为了创建新的子类对象而再次执行一遍类加载,但是静态块已经加载完毕,无需再次加载,所以此时仅加载构造代码块和构造器
- 得到引用对象,执行代码
接口
- 在有继承关系的类中引用多个不同接口时,需要注意接口内定义的常量名称不可重复,如果重复在调用常量时,编译器会提示The field Father.v is ambiguous(所使用的字段不明确)
- 接口内的常量初始化是在被调用时初始化,当且仅当仅调用接口中的某常量时,运行且不会影响实现接口的类,例如类Foo实现了接口Bar,Bar中有常量S,使用Foo.S仅会初始化S,不会调用Foo内部任何成员。