JAVA类初始化及实例初始化时内部的执行顺序

       记得刚毕业时,应聘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	<clinit>;
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	<init>;
const #25 = Method	#3.#26;	//  java/lang/Object."<init>":()V
const #26 = NameAndType	#24:#6;//  "<init>":()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."<init>":()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."<init>":()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 "<init>":()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中会生成一个叫<clinit>的方法(第11行),普通块和构造方法合在一起会生成一个叫<init>的方法(第30行)。

     总的来说:类初始化属于类加载的最有一个阶段(主要在方法区工作),会先执行<clinit>()(有静态变量和静态块组成,详细说明看后文);然后执行普通成员变量,当初始化实例时(也是对象初始化,实例初始化,相当于用new在堆中创建对象),会先执行<init>(),也就是构造方法(经过编译器处理后,普通块被放到构造方法中去了)。

    普通块是不是被编译期嵌入到构造器中去了呢?下面写个程序对比一下,编译前和编译后代码的样子就知道了。

编译前的代码

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是先加载父类然后再加载子类,也就是说先执行父类的<clinit> 再执行子类的<clinit>;接着是对象初始化了,由运行结果可知,也是先实例初始化父类,然后再实例初始化子类。也许有人问,我没有new父类啊,父类怎么也实例初始化了?是因为当你new一个子类时,JVM会先检查子类是否有父类,有的话则先new父类。上面那题,执行new A()时,检查到有父类P,就先new P(),但是又检查到P有父类Object,所以又先new Object();最后的顺序是new Object()->new P()->new A();其实类初始化和实例初始化过程也是一样的,先父类,后子类。

一句话:先父类的<clinit>(),后子类的<clinit>();先父类的<init>(),后子类的<init>()

      上面提到了<clinit()>,那它包含什么东西呢?

      可知,<clinit>()方法包含了类中所有的类变量(用static修饰的变量,方法中的局部变量是不能用static修饰的,除非是final static这样修饰)和静态语句块(static{}块),它们在<clinit>()中的顺序和程序中的写的顺序一样。静态语句块中只能访问到定义在它之前的变量(类变量),不能访问定义在它之后的类变量,但可以赋值。在eclipse下写点代码验证了一下,发现编译时就提示错误了,呵呵。

     上面说了<clinit>()中的顺序和程序中的顺序一样的,如果静态变量在静态块前,会先执行静态变量。下面看个例子,直观点。

 

public class A {
	public A(){
		System.out.println("我是A类构造器");
	}
	public A(String a){
		System.out.println("我是A类构造器2"+a);
	}
	{
		System.out.println("我是A类普通块");
	}
	static{
		System.out.println("我是A类静态块");
	}
}


public class B {
  static A a=new A("   hello!!");
  static{
	  System.out.println("我是B类中的第一个静态块");
  }
}


public class Test {
	public static void main(String[] args){
		new B();
	}
}


    运行结果

     我是A类静态块
     我是A类普通块
     我是A类构造器2   hello!!
     我是B类中的第一个静态块

  这里没使用基础,但是却先执行了A,可见上面的说法没错。

  来个初始化顺序图

 

  下面来看一题有趣的题
public class T {
 public static T t=new T();
 public static  int a=0;
 public static  int b=1;
  private T(){
	  a++;
	  b++;
	  System.out.println("构造方法中:a="+a+"    b="+b);
  }
  public static T getInstance(){
	  return t;
  }
  public static void main(String[] args){
	  T tt=T.getInstance();
	  System.out.println("main方法中:tt.a="+tt.a+"   tt.b="+tt.b);
	  
  }
}


你心中的答案是什么呢????呵呵,来看看结果,是否和你心中的答案一样。

构造方法中:a=1    b=1
main方法中:tt.a=0   tt.b=1

 

   这就要说说类加载和类初始化,实例化赋值了。

1.JVM要执行main方法时,要先加载T类文件(class文件),这时的变量都有一个默认值。如程序中t=null,a=0(这个0是默认的0,不是程序上写的0),b=0

2.类初始化,先执行<clinit>(),而<clinit>()中有public static T t=new T();public static int a=0;public static int b=1。先执行第一句public static T t=new T(),执行这句,那就相当于new一个对象(实例化对象),它会先执行<init>(),接着执行private T()方法了,但是此时a=0,b=0;所以a++,b++后,a=1,b=1。和运行结果一致。然后再回到<clinit>()中执行public static int a=0(这个0就是程序上写的0),public static int b=1;这个时候a=0,b=1了,而不再是a=1,b=1了。

3.初始化结束了,然后执行到main方法中的T tt=T.getInstance(),这时tt指向的对象中的a=0,b=1

 

那么如果我把程序中的变量顺序调一下呢

public class T {
 public static  int a=0;
 public static  int b=1;
 public static T t=new T();
  private T(){
	  a++;
	  b++;
	  System.out.println("构造方法中:a="+a+"    b="+b);
  }
  public static T getInstance(){
	  return t;
  }
  public static void main(String[] args){
	  T tt=T.getInstance();
	  System.out.println("main方法中:tt.a="+tt.a+"   tt.b="+tt.b);  
  }
}


这时的结果,聪明的你,肯定非常清楚了。

构造方法中:a=1    b=2
main方法中:tt.a=1   tt.b=2

    上面的a,b都用了static修饰,那如果去掉static呢?

public class T {
	 public static T t=new T();
	 public   int a=0;
	 public   int b=1;
	  private T(){
		  a++;
		  b++;
		  System.out.println("构造方法中:a="+a+"    b="+b);
	  }
	  public static T getInstance(){
		  return t;
	  }
	  public static void main(String[] args){
		  T tt=T.getInstance();
		  System.out.println("main方法中:tt.a="+tt.a+"   tt.b="+tt.b);
		  
	  }
	}

构造方法中:a=1    b=2
main方法中:tt.a=1   tt.b=2

再调一下public static T t=new T();的位置

public class T {
	
	 public   int a=0;
	 public   int b=1;
	 public static T t=new T();
	  private T(){
		  a++;
		  b++;
		  System.out.println("构造方法中:a="+a+"    b="+b);
	  }
	  public static T getInstance(){
		  return t;
	  }
	  public static void main(String[] args){
		  T tt=T.getInstance();
		  System.out.println("main方法中:tt.a="+tt.a+"   tt.b="+tt.b);
		  
	  }
	}

构造方法中:a=1    b=2
main方法中:tt.a=1   tt.b=2

   发现结果都一致,为什么和a,b都加了static那2题不一样了呢?下面来说说a,b都不加 static修饰的这2题的执行顺序.

1.加载class文件时,成员变量(不管是类变量还是实例变量)都先给一个默认值,此时t=null,a=0,b=0

2.类初始化.<clinit>()中只有public static T t=new T();所以执行new T(),而执行new T()就是创建对象(实例化对象),此时接着执行<init>(),会先执行普通成员变量a=0,b=1,所以执行到这,a=0,b=1了。接着执行T()方法,a++,b++,这是a=1,b=2了。所以构造方法中打印a=1,b=2;然后,再返回<clinit>(),但是此时<clinit>()没有其他的了,所以a=1,b=2就保持了下来。

关于块给变量赋值、构造器给变量赋值的执行顺序可以参考此博文http://java-mzd.iteye.com/blog/838683

 

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值