从Java执行过程开始
class Print{
Print(String s){
System.out.println("Step "+s);
}
Print(String s,double i){
System.out.println("Step "+s+" 随机数 "+i);
}
}
abstract class Glyph {
abstract void draw();
Print p2 = new Print("父类成员变量");
static Print p3= new Print("父类静态成员变量");
static Print p6= new Print("父类静态成员变量",Math.random());
Glyph() {
System.out.println("父类构造方法");
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph {
int radius = 1;
String s = "abcdefg";
static Print p4= new Print("子类静态成员变量");
static Print p5= new Print("子类静态成员变量",Math.random());
Print p1 = new Print("子类成员变量");
RoundGlyph(int r) {
System.out.println("子类构造方法");
radius = r;
System.out.println(
"RoundGlyph.RoundGlyph(), radius = "
+ radius);
}
void draw() {
System.out.println("子类draw方法, radius = " + radius+" 字符串 "+s);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
运行结果如下:
Step 父类静态成员变量
Step 父类静态成员变量 随机数 0.8817076586261154
Step 子类静态成员变量
Step 子类静态成员变量 随机数 0.937529828526132
Step 父类成员变量
父类构造方法
Glyph() before draw()
子类draw方法, radius = 0 字符串 null
Glyph() after draw()
Step 子类成员变量
子类构造方法
RoundGlyph.RoundGlyph(), radius = 5
结果分析:(根据执行结果大概分析)
当调用子类的构造方法时,会使触发类的初始化。首先类装载器装载子类,发现子类有继承时再装载父类,此时并没有对类进行初始化,在采取其他任何操作之前,为对象分配的存储空间初始化成二进制零。然后再按类的初始化顺序依次初始化成员变量:
首先初始化父类静态语句,其次是成员变量,最后是构造方法,然后初始化子类。
这是大概流程,还有很多问题:
1.为什么子类加载到jvm知道要去加载父类,应为类的加载只是把类的二进文件流加载到内存,还没有执行任何代码,怎么知道去加载父类?
类的具体加载过程
首先,那些情况会使系统去加载一个类:
1.遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,假如类还没进行初始化,则马上对其进行初始化工作。其实就是3种情况:用new实例化一个类时、读取或者设置类的静态字段时(不包括被final修饰的静态字段,因为他们已经被塞进常量池了)、以及执行静态方法的时候。
2.使用java.lang.reflect.*的方法对类进行反射调用的时候,如果类还没有进行过初始化,马上对其进行。
3.初始化一个类的时候,如果他的父亲还没有被初始化,则先去初始化其父亲。
4.当jvm启动时,用户需要指定一个要执行的主类(包含static void main(String[] args)的那个类),则jvm会先去初始化这个类。
然后,系统正式加载一个类:
这一块虚拟机要完成3件事:
1.通过一个类的全限定名来获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3.在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
此时系统还没有执行任何代码,接下来是对类的检验,验证是否符合规范等
包括文件格式验证->元数据验证->字节码验证->符号引用验证
接下来进入准备阶段,这时应该就是上面粗略结果分析的:在采取其他任何操作之前,为对象分配的存储空间初始化成二进制零
此时也并没有对静态成员赋值
在对二进制文件进行解析后就进入初始化了:
Object应该是第一个被初始化的
此时开始执行真正的代码,第一个被执行的是编译时系统自动添加的<clinit>()方法,该方法是类加载时执行的第一个方法,<clinit>();方法与类构造方法不一样,他不需要显示得调用父类的<clinit>();方法,虚拟机会保证子类的<clinit>();方法在执行前父类的这个方法已经执行完毕了,也就是说,虚拟机中第一个被执行的<clinit>();方法肯定是Object类的<clinit>()方法。该方法只执行一次。
RoundGlyph类的<clinit>()方法:
可以看出,所有静态成员都在这里初始化
到上一步,类的加载已经完成,接下来就是类的初始化:
类的初始化是系统执行<init>方法(这个也是系统自动添加)查看init方法可以看出,init执行的顺序是:先初始化成员变量,最后再调用类的构造方法,所以构造方法总是最后调用
类文件中成员变量的存贮
public class TestMain {
private int i = 222222;
private int i1 = 11;
private int i22 = 22;
private long l = 66666;
private double d = 99999;
private Other o = new Other();
private static String s = "abc";
private static String s2 = "abcdef";
private String s3 = "abcdefghijk";
TestMain(){
i = 2;
}
public void f(){
i = 111111;
System.out.println("test");
}
public static void main(String[] args) {
new TestMain();
}
}
其中对于int型的i,定义值为222222,存放在常量池,i1和i22直接用立即数入栈,直接在指令中保存数据。