Java类加载机制
分为五步:加载、验证、准备、解析、初始化。
- 加载:通过类全名获取类的二进制流,装入方法区,生成Class对象
- 验证:验证二进制流是否符合格式
- 准备:为类的静态变量划分空间
- 解析:将常量池内的符号引用转化为直接引用
- 初始化:执行静态代码块,为静态变量赋初值
几种类加载器:
- 启动类加载器
- 拓展类加载器
- 应用类加载器
- 自定义类加载器
双亲委派机制
核心思想是,当类加载器要加载一个类时,会先将加载任务委托给父类加载器,这个过程会一直递归直到最顶层的启动类加载器。只有当父类加载器无法完成加载任务时,才由子类加载器执行。
优点:
- 避免类的重复加载
- 防止核心API被注入篡改,已经被加载过的类不会重复加载,避免了被篡改注入恶意代码的类被加载
Java中的static
静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this
的,因为它不依附于任何对象。既然没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。
我们最常见的static方法就是main方法,而main方法必须是static的,是因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。
Java规定static不能用来修饰局部变量。
与C/C++中的static不同,Java中的static关键字不会影响到变量或者方法的作用域
1 | public class Main { |
Q:Java中可以通过this
访问静态成员变量吗?下面的代码输出是什么?
1 | public class Test { |
A:可以,输出为0
。
Q:以下代码输出是什么?
1 | public class Father { |
A:输出为
1 | Father中的静态代码块1 |
要运行静态函数main,首先要加载Father
类。加载Father类时按顺序执行static
块,输出前两行。
随后执行main
函数,输出第三行。
再实例化Son
。实例化前要先加载Son类,又因Son继承Father,则要先加载Father和Son,而Father已经加载了,于是加载Son,执行Son中的static块,输出第四行。
接着要实例化Son,在构造函数前要先初始化成员变量。因为继承Father所以要先初始化Father,Father有个Other
类型的成员变量和一个非static块,按照先后顺序执行,又需要先加载并实例化Other。
于是加载Other,static块输出第五行。Other的非static块输出第六行,构造函数输出第七行。
接着Father初始化完成员变量继续按顺序执行非static块,输出第八行。
现在Son的父类Father成员变量已经初始化,于是Father的构造函数输出第九行。
Son的父类结束,轮到Son初始化成员变量,非static块每次实例化一个对象都会执行一次,因此Other的非static块再次执行输出第十行。其构造函数再输出第十一行。
最后Son的构造函数输出最后一行。Son实例化结束,main函数结束。