既然是杂谈,那么肯定会比较杂乱,都是点到为止,不回做过多的原理分析,如果对于原理,可能需要深入了解JVM内部的东西了。<br/> 首先,我来展示一下几个现象,然后对这几个现象进行分析。<br/> 相关几个类的定义
<!-- lang:java-->
package com.bieber.gc;
public class Super {
static{
System.out.println("super static block");
}
public Super(){
System.out.println("super init");
}
public void test(){
System.out.println("invoke test");
}
}
public class Main extends Super{
public Main() {
System.out.println("main init");
}
static{
System.out.println("sub static block");
}
public void test(){
System.out.println("main test");
}
}
这里涉及到两个类,下面针对这两个类来写几个测试用例。<br/> ####用例一
用例代码
<!-- lang:java-->
public static void main(String[] args) throws Exception {
try {
System.out.println("#########start load class##########");
Thread.currentThread().getContextClassLoader().loadClass("com.bieber.gc.Main");
System.out.println();
System.out.println("#########start init class#########");
Main main = new Main();
System.out.println();
System.out.println("#########invoke#########");
main.test();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
#####输出结果
<!-- lang:java -->
#########start load class##########
#########start init class#########
super static block
sub static block
super init
main init
#########invoke#########
main test
#####结果分析 可以看到通过Classloader加载类的时候,并没有执行static代码块,而static代码块却放到了调用构造函数的时候执行了,并且static代码块是在构造函数之前执行的。同时在调用在子类重写的方法时候,父类方法没有执行(这点是毫无疑问的)。 ####用例二 #####用例代码
<!-- lang:java -->
public static void main(String[] args) throws Exception {
try {
System.out.println("#########start load class##########");
Class.forName("com.bieber.gc.Main", true, Thread.currentThread().getContextClassLoader());
System.out.println();
System.out.println("#########start init class#########");
Main main = new Main();
System.out.println();
System.out.println("#########invoke#########");
main.test();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
#####执行结果
<!-- lang:java -->
#########start load class##########
super static block
sub static block
#########start init class#########
super init
main init
#########invoke#########
main test
#####结果分析
发现这个时候子类和父类的static代码块在调用构造函数之前执行的,这点说明此处的Class.forName
和ClassLoader
执行效果一样。其他部分和上面基本一致。
####用例三
#####用例代码
<!-- lang:java -->
public static void main(String[] args) throws Exception {
try {
System.out.println("#########start load class##########");
Class.forName("com.bieber.gc.Main", false, Thread.currentThread().getContextClassLoader());
System.out.println();
System.out.println("#########start init class#########");
Main main = new Main();
System.out.println();
System.out.println("#########invoke#########");
main.test();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
#####执行结果
<!-- lang:java -->
#########start load class##########
#########start init class#########
super static block
sub static block
super init
main init
#########invoke#########
main test
#####结果分析 这个执行结果和和用例一的结果一样。
####总结
上面列举了三个测试用例,用例二和用例三的却别就是
Class.forName("com.bieber.gc.Main", true, Thread.currentThread().getContextClassLoader());
和Class.forName("com.bieber.gc.Main", false, Thread.currentThread().getContextClassLoader());
的区别,这个很容易理解,其实第一个是在加载类的时候是初始化了类(Class)的其他信息,而第二个是延迟初始化。如果是延迟初始化从执行结果上来看和hread.currentThread().getContextClassLoader().loadClass("com.bieber.gc.Main");
是一样的。<br/>
通过上面可以理解为,类的加载分为两个步骤:<br/>
- 只是装载类,类似将类的字节码加载到JVM中<br/>
- 解析类的结构,并初始化类的相关信息,比如static代码块<br/>
并且可以得到父类的加载总是在子类之前,不管是static代码执行,还是构造函数的调用。这也很容易理解,因为子类中可能会调用父类的信息,所以先需要将父类准备好了,子类调用的时候,才没问题。<br/>
此处给一个建议:谨慎在父类中定义protected(public) static的属性,因为这个时候,是所有子类共用一份内存,很容导致读写冲突。<br/>
针对上面的测试,下面列举出几个其他的测试,大家看看会得到什么结果?
<!-- lang:java -->
package com.bieber.gc;
public class Field {
static{
System.out.println("field static block");
}
}
public class Main extends Super{
private static Field field=new Field();
public Main() {
System.out.println("main init");
}
static{
System.out.println("sub static block");
}
public void test(){
System.out.println("main test");
}
}
或者
public class Main extends Super{
private static Field field;
public Main() {
System.out.println("main init");
}
static{
System.out.println("sub static block");
}
public void test(){
System.out.println("main test");
}
}
我把Main
类做了上面的调整,看看执行上面的测试用例,会得到什么结果。欢迎讨论交流,也希望大家也能分享一下你们在开发过程中的一些东西。
来源:oschina
链接:https://my.oschina.net/u/1384459/blog/286893