前言:
本篇博客接续上一篇中未写到的部分,分为类的主动加载和被动加载,可能涉及到一些其他内容。依旧使用先思考做题后在看答案的策略。
阅读须知:
此次博客以启发性代码和解释进行学习。在阅读时,按照代码和提示进行思考分析为什么,请思考过后在看答案来验证自己的思考。(此博客为个人观点且本人水平有限,如有错,请批评指正)
小题测试:(先思考,并写出自己的答案后在往下看)
package com.wen.demo.test.MyClass;
class Parent {
static {
System.out.println("父亲初始化");
}
}
class Childen extends Parent {
static {
System.out.println("儿子初始化");
}
}
public class MyTest {
public static void main(String[] args) {
new Childen();
}
}
对于以上代码,请先思考,输出的是什么。然后才往下面看。
。
。
。
。
概念:
主动使用:
在类的初始化阶段,虚拟机规范严格规定了有且只有只有五种情况必须对类进行“初始化”,这里我在细分一些,分为6种情况。
(1)创建类的示例。例如:new 一个对象
(2)访问某个类的静态变量或给静态变量赋值
(3)调用一个类的静态方法
(4)反射
(5)初始化一个子类(继承了父类)
(6)虚拟机启动时,用户指定一个启动类(即含有main()的那个类)会优先初始化
被动使用:
被动引用不会导致类的初始化,这里先介绍几种被动引用的方法。如下代码:
第一种被动引用情况:
package com.wen.demo.test.MyClass;
/**
* 被动使用简单示例
*/
class Parent {
static {
System.out.println("父亲初始化");
}
/**
* 第一种,父类定义静态变量,被子类引用,不会导致子类初始化
*/
public static int A=10;
}
class Childen extends Parent {
static {
System.out.println("儿子初始化");
}
}
public class MyTest {
public static void main(String[] args) {
System.out.println(Childen.A);
}
}
第一种情况,我们要理解上面所说的主动使用,访问某个类的静态变量或给静态变量赋值,父类拥有这个静态字段,通过子类引用是不会导致子类初始化的。
我们只要认定,对于静态字段,只有直接定义这个字段的类才会被初始化,就可以了~!
第二种被动引用情况:
package com.wen.demo.test.MyClass;
/**
* 被动使用简单示例
*/
class Parent {
static {
System.out.println("父亲初始化");
}
}
public class MyTest {
public static void main(String[] args) {
//注意看这里,看清楚是个数组
Parent[] parents= new Parent[10];
}
}
对于这种情况,大家可能懵逼了~不是用了new关键字吗?不是对象吗?
运行后,我们会没有发现有“父亲初始化”这几个字输出来。其实,在运行时,其实数组已经不是Parent类型了,Parent的数组jvm在运行期,会动态生成一个新的类型,新类型为为L+包名+类名。这个类型是由虚拟机自动生成的,直接继承自java.lang.Object的子类。已经不是原本我们自己写的包+类名了。
第三种被动引用情况:
package com.wen.demo.test.MyClass;
/**
* 被动引用简单示例
*/
class Parent {
static {
System.out.println("父亲初始化");
}
public static final int value=10;
}
public class MyTest {
public static void main(String[] args) {
System.out.println(Parent.value);
}
}
这里直接用类名.静态字段来调用,如果是变量当然是会初始化了。但是final修饰后已经在编译期存入了调用类的常量池中。这个和上一篇博客中有介绍,这里就不多介绍了。
测试题结果:
我们实例化了子类,可是却先初始化了父类,才初始化子类。为什么?这个就要从JVM的双亲委派模型说起了。先看下图:
先介绍双亲委派模型的工作流程:
如果一个类加载器收到了类的加载请求,他首先不会自己去尝试去加载这个类,而是将这个请求委派给父类加载器去完成。每一层加载器都是这样委派,因此所有的加载请求都会到最顶层的启动了加载器中,假如启动类加载器加载不了,反馈信息给子类加载器,子类加载器在尝试加载,最终可能又回到最开始的自定义类加载器来加载。如果都加载不了了。那就会抛出异常,这就是我们平时见到的ClassNotFoundException异常的由来了。
简单来说就是:今天吃完饭要洗碗的,我们那么懒肯定不洗就丢给爸妈去洗,他们也不肯,就丢给爷爷奶奶去洗,以此类推,当最顶的都不想洗碗,就告诉下一级子类,依次传递最终自己来洗也是有可能的,当然,父母大发慈悲另说。(这里只是随便说说,大致了解一下就好~当真你就输了)
额外补充一下:(类加载器)
-
启动类加载器,由C++实现,没有父类。
-
拓展类加载器(Extension ClassLoader),由Java语言实现,父类加载器为null
-
系统类加载器(Application ClassLoader),由Java语言实现,父类加载器为Extension ClassLoader
上述是我们应用程序常用的类加载器,如果有必要,还可以加入我们自己定义的类加载器~!每个加载器负责的东西这里就不介绍了,想了解的自己百度一下~!
总结:
一天又结束了,看了书才会知道自己的基础到底有多差,整个大学快过去了,也许打基础的时间久剩下一年了。出去工作后想要打地基就更加难了。不管出去工作还是在学习,还是需要时刻充电了。最后祝大家学习进步,工作愉快~!