`
ftj20003
  • 浏览: 130489 次
  • 性别: Icon_minigender_1
  • 来自: ...
社区版块
存档分类
最新评论

号称放倒一片的一道J2SE基础题的个人理解

    博客分类:
  • Java
阅读更多
    近日无意中看到一道Java基础题,号称在接受测试的1000名人中,仅有1.5%的程序员或者项目经理完全正确。原题是3个小程序并要求按顺序答题,我修改了一下弄成一个例子。个人觉得虽然是三个稍微不同的例子,但是考察的本质都是一样,只要了解了原理就是100个变种也还是表达一个意思。先来看看例子:
/**
 *
 * @author: yanxuxin
 * @date: 2010-1-18
 */
public class Child extends Father{
	
	static {
		System.out.println("child-->static");
	}
	
	private int n = 20;
	
	{
		n = 30;
	}
	
	public int x = 200;
	
	public Child() {
		this("The other constructor");
		System.out.println("child constructor body: " + n);
	}
	
	public Child(String s) {
		System.out.println(s);
	}
	
	public void age() {
		System.out.println("age=" + n);
	}
	
	public void printX() {
		System.out.println("x=" + x);
	}
	
	public static void main(String[] args) {
		new Child().printX();
	}
}

class Father {
	
	static {
		System.out.println("super-->static");
	}
	
	public static int n = 10;
	public int x = 100;
	
	public Father() {
		System.out.println("super's x=" + x);
		age();
	}
	
	public void age(){
		System.out.println("nothing");
	}
}

这个看起来很孔乙己的例子的输出是什么呢?

     下面说说我个人理解的过程:
      (1).Child的main(...)方法内的new语句会触发JVM对类的加载。因为Child是继承自Father的,所以会先加载Father.class.加载过程会执行两步:
(a).所有的static数据域初始化为默认值(0,false,null),所以Father内的static的n首先默认值为0.
(b).执行所有的static域的初始化语句或者初始化块(按出现的顺序),此时Father内static块的打印语句执行:super-->static,并且static的n赋值语句n = 10执行。
父类的方法表建立后算是父类加载完毕,接着会加载子类Child,执行的过程与父类类似。因为子类没有static的数据域,所以仅仅static的初始化块内的打印语句执行:child-->static
这也解释了为什么static的初始化只有一次,因为它仅仅在类加载的时候执行而与实例化没有关系。

      (2).需要的类加载完毕之后new Child()开始使用类构造器创建实例。我理解的构造器的执行有以下几步:
(a)所有数据域初始化为默认值(0,false,null)。
(b)如果有父类则先执行父类的构造器。
(c)按声明出现的顺序执行所有数据域的初始化语句或者初始化块
(d)如果构造器主体的第一句调用了另一个构造器,则执行第二个构造器的主体(非super)
(e)执行构造器的主体部分
现在对号入座看看此例的情况,首先Child的数据域初始化为默认值:n=0, x=0.接着由于继承Father如果没有在构造器主体显示使用super(...)调用Father有参或者无参的构造器,则隐式的去调用无参的构造器。此时开始了Father构造器的执行过程:
     首先Father的所有数据域初始化为默认值:x=0;接着是Father的父类Object的构造器执行;完毕后Fahter开始数据域的初始化语句的执行:x=100;Father构造器主体没有调用自身的其他的构造器,第四步跳过;最后一步就是Father构造器的主体:先执行打印super's x=100,接着调用age().这里Father和Child的方法表内都有age()方法,但是因为要创建的是Child实例,所以JVM会动态的绑定到Child的age()方法,所以执行了System.out.println("age=" + n);而此时n仅仅是默认值0,所以打印age=0.
     父类的构造器调用完毕之后,继续Child构造器执行的第三步:数据域的初始化语句和初始化块,此时按照声明的顺序n=20,接着在初始化块中n=30,然后就是x=200;Child的第四步满足条件,所以会执行Child(String s)有参构造器的主体,打印The other constructor;最后一步执行本构造器的主体,打印child constructor body: 30
   
    至此实例化完毕,main函数内调用了实例的printX()方法执行System.out.println("x=" + x)语句。这里虽然Father和Child都有public的x。但是由于语句内没有显示使用super.x,则会隐式的使用this.x。这样打印x=200.完整的执行结果如下:
super-->static
child-->static
super's x=100
age=0
The other constructor
child constructor body: 30
x=200

   
    这个小题目虽然是基础题,但是涉及到整个实例化的过程,相对还是有些迷惑的。原题拆分成三个所以很容易得到正确的答案,很遗憾的找不到原题了,所以设计了这个看起来很混乱的小例子来分析。很大的体会是之前觉得知道JVM原理也没什么用处,现在发现要想深入的掌握一门语言,基础的原理是需要知道的。比如volatile为什么禁用了寄存器的写入直接写回内存就能保证了可见性?因为JVM中寄存器和栈都是线程独享的,而堆和方法区是线程共享的。这些了解的深刻,将有助于提高代码的正确率。
2
0
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

Global site tag (gtag.js) - Google Analytics