记得刚毕业时,应聘JAVA开发岗位,做招聘单位的笔试时,经常有JAVA类内部的执行顺序的考察,就是让你写出某个程序的打印结果的顺序,现在整理一下。
如果一个类,有构造器,普通块,静态块,那该类初始化时,它的执行顺序如何呢?如果它有父类,并且它的父类也有构造器,普通块,静态块呢?直接写个小程序,测一下,就一目了然。
public class A {
public A(){
System.out.println("我是构造器");
}
{
System.out.println("我是普通块");
}
static{
System.out.println("我是静态块");
}
public static void main(String[] args){
new A();
}
}
运行结果是:
我是静态块
我是普通块
我是构造器
顺序是如何的,就不用我多说了,一目了然。那么下面来看看,它编译后的样子,是不是顺序也是这样的。
Compiled from "A.java"
public class test.A extends java.lang.Object
SourceFile: "A.java"
minor version: 0
major version: 50
Constant pool:
const #1 = class #2; // test/A
const #2 = Asciz test/A;
const #3 = class #4; // java/lang/Object
const #4 = Asciz java/lang/Object;
const #5 = Asciz ;
const #6 = Asciz ()V;
const #7 = Asciz Code;
const #8 = Field #9.#11; // java/lang/System.out:Ljava/io/PrintStream;
const #9 = class #10; // java/lang/System
const #10 = Asciz java/lang/System;
const #11 = NameAndType #12:#13;// out:Ljava/io/PrintStream;
const #12 = Asciz out;
const #13 = Asciz Ljava/io/PrintStream;;
const #14 = String #15; // 我是静态块
const #15 = Asciz 我是静态块;
const #16 = Method #17.#19; // java/io/PrintStream.println:(Ljava/lang/String;)V
const #17 = class #18; // java/io/PrintStream
const #18 = Asciz java/io/PrintStream;
const #19 = NameAndType #20:#21;// println:(Ljava/lang/String;)V
const #20 = Asciz println;
const #21 = Asciz (Ljava/lang/String;)V;
const #22 = Asciz LineNumberTable;
const #23 = Asciz LocalVariableTable;
const #24 = Asciz ;
const #25 = Method #3.#26; // java/lang/Object."":()V
const #26 = NameAndType #24:#6;// "":()V
const #27 = String #28; // 我是普通块
const #28 = Asciz 我是普通块;
const #29 = String #30; // 我是构造器
const #30 = Asciz 我是构造器;
const #31 = Asciz this;
const #32 = Asciz Ltest/A;;
const #33 = Asciz main;
const #34 = Asciz ([Ljava/lang/String;)V;
const #35 = Method #1.#26; // test/A."":()V
const #36 = Asciz args;
const #37 = Asciz [Ljava/lang/String;;
const #38 = Asciz SourceFile;
const #39 = Asciz A.java;
{
static {};
Code:
Stack=2, Locals=0, Args_size=0
0: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #14; //String 我是静态块
5: invokevirtual #16; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 12: 0
line 3: 8
public test.A();
Code:
Stack=2, Locals=1, Args_size=1
0: aload_0
1: invokespecial #25; //Method java/lang/Object."":()V
4: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #27; //String 我是普通块
9: invokevirtual #16; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #29; //String 我是构造器
17: invokevirtual #16; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return
LineNumberTable:
line 5: 0
line 9: 4
line 6: 12
line 7: 20
LocalVariableTable:
Start Length Slot Name Signature
0 21 0 this Ltest/A;
public static void main(java.lang.String[]);
Code:
Stack=1, Locals=1, Args_size=1
0: new #1; //class test/A
3: invokespecial #35; //Method "":()V
6: return
LineNumberTable:
line 16: 0
line 17: 6
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 args [Ljava/lang/String;
}
从第1行到45行,或者从47行到99行,都可以看出执行顺序。
从第1行到45行,可以看到,static{}块在JVM中会生成一个叫
总的来说:类初始化属于类加载的最有一个阶段(主要在方法区工作),会先执行
普通块是不是被编译期嵌入到构造器中去了呢?下面写个程序对比一下,编译前和编译后代码的样子就知道了。
编译前的代码
public class B {
public static String s="abc";
static{
System.out.println("我是静态块");
}
public int a=1;
public B(){
System.out.println("我是构造器"+a);//打印后,发现a=1,而不是加载时a为默认值0,
// 说明普通成员的优先于构造方法执行
}
{
System.out.println("我是普通块");
}
public B(String ss){
int c=2;
System.out.println("我是构造器"+c);
}
public static void main(String[] args){
new B();
new B("");
}
}
编译后用反编译软件看到的代码
public class B
{
public static String s = "abc";
public int a = 1;
static
{
System.out.println("我是静态块");
}
public B()
{
System.out.println("我是普通块");
System.out.println("我是构造器" + this.a);
}
public B(String ss) {
System.out.println("我是普通块");
int c = 2;
System.out.println("我是构造器" + c);
}
public static void main(String[] args) {
new B();
new B("");
}
}
通过对比可看出,普通块都被编译期放到了所有的构造方法中,而且是方法体里面第一行。
那么如果父类中也有静态块,普通块,构造器呢?先看看程序。
public class P {
public P(){
System.out.println("我是父类构造器");
}
{
System.out.println("我是父类普通块");
}
static{
System.out.println("我是父类静态块");
}
}
public class A extends P{
public A(){
System.out.println("我是子类构造器");
}
{
System.out.println("我是子类普通块");
}
static{
System.out.println("我是子类静态块");
}
public static void main(String[] args){
new A();
}
}
运行结果是:
我是父类静态块
我是子类静态块
我是父类普通块
我是父类构造器
我是子类普通块
我是子类构造器
由于静态块是在类初始化的时候就执行了,所以最先输出,这点没问题。再看运行结果可知,JVM是先加载父类然后再加载子类,也就是说先执行父类的
一句话:先父类的
上面提到了
可知, |
上面说了
我是A类静态块 这里没使用基础,但是却先执行了A,可见上面的说法没错。 来个初始化顺序图
|
下面来看一题有趣的题 |
构造方法中:a=1 b=1
这就要说说类加载和类初始化,实例化赋值了。 1.JVM要执行main方法时,要先加载T类文件(class文件),这时的变量都有一个默认值。如程序中t=null,a=0(这个0是默认的0,不是程序上写的0),b=0 2.类初始化,先执行 3.初始化结束了,然后执行到main方法中的T tt=T.getInstance(),这时tt指向的对象中的a=0,b=1
那么如果我把程序中的变量顺序调一下呢
构造方法中:a=1 b=2 |
上面的a,b都用了static修饰,那如果去掉static呢? 构造方法中:a=1 b=2 |
再调一下public static T t=new T();的位置 构造方法中:a=1 b=2 |
发现结果都一致,为什么和a,b都加了static那2题不一样了呢?下面来说说a,b都不加 static修饰的这2题的执行顺序. 1.加载class文件时,成员变量(不管是类变量还是实例变量)都先给一个默认值,此时t=null,a=0,b=0 2.类初始化. |
关于块给变量赋值、构造器给变量赋值的执行顺序可以参考此博文http://java-mzd.iteye.com/blog/838683 |