Java面试系列第3篇-Java对象

 下面介绍Java面试中常见的对象加载及创建题目。

1、Java对象初始化顺序

先看一下如下笔试题目:

class Parent {
	public static int a = 2;
	public int b = 3;
	// 2
	{
		System.out.println("this is anonymity b=" + b);
	}
	// 1
	static {
		a = 4;
		System.out.println("this is static and a=" + a);
	}

	// 3
	public Parent() {
		System.out.println("this is Parent constructor b="+b);
	}

}

public class Son extends Parent {
	public int b = 0;
	// 4
	public Son() {
		System.out.println("this is Son constructor b="+b);
	}


	public static void main(String[] args) {
		new Son();
	}
}

当创建对象时,涉及到静态和非静态变量的初始化、静态和非静态匿名块的初始化以及构造函数的初始化,所以好多面试者不容易记住也不容易理解,下面我把这个类重构一下,变为如下的形式:

class Parent {
	public static int a;
	public int b;
	
	
	public <cinit>(){
		a = 2;
		
		static {
			a = 4;
			System.out.println("this is static and a=" + a);
		}
	}
	
	public <init>(){
		b = 3;
		 
		System.out.println("this is Parent constructor b="+b);
		 
		{
			System.out.println("this is anonymity b=" + b);
		}
	}
}

public class Son extends Parent {
	public <cinit>(){
		
	}
	
	public <init>(){
		System.out.println("this is  Son constructor");
	}
	
	// ...
}

Javac编译器在生成字节码之前会将类调整为如上的样子,其中合成的函数<cinit>()可以看作静态构造函数,在类的加载阶段调用,而合成的函数<init>()可以看作是实例构造函数,在对象创建的时候调用。调用子类的<cinit>()方法之前必会先调用父类的<cinit>()方法,这是由类的加载顺序决定的。等类加载完成后就可以创建对象了,同样初始化子类必先初始化父类,也就是先调用父类的<init>()方法,后调用子类的<init>()方法。那么现在的调用顺序就是:

Parent.<cinit>() -> Son.<cinit>() -> Parent.<init>() -> Son.<init>()  

在重构<cinit>()方法时,将变量的初始化写在前面,匿名块写在后面;在重构<init>()方法时,将变量的初始化写在前面,将构造函数原有的内容写在中间,最后写匿名块的内容。如果有多个变量或匿名块,就按源代码的顺序写即可。

那么打印的结果肯定一目了然了,打印的结果如下:

this is static and a=4
this is anonymity b=3
this is Parent constructor b=3
this is Son constructor b=0

如果还想了解更多,比如Javac编译器为什么要进行这样的高速,可以参考《深入解析Java编译器:源码剖析与实例详解》一书。 

2、对象创建的几种方式

 要熟记对象创建的几种方式,如下:

(1)使用new 关键字 使用 new 关键字创建对象,实际上是做了两个工作,一是在内存中开辟空间,二是初始化对象;

(2)使用反射创建对象 反射创建对象分为两种方式,一是使用Class类的newInstance() 方法,二是使用Constructor类的newInstatance() 方法。Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数; Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数;

(3)使用clone()方法;

(4)使用序列化。

最广泛使用的应该就是使用new关键字和使用反射创建了。对于反射创建来说,如下:

Class.forName(className).newInstance();

调用forName()只会加载类,要得到对象需要调用newInstance()方法。这种创建方式在框架中用的多,就是因为forName()方法的参数是字符串类型,通过这个字符串来指定要加载的类,我们就可以通过配置的形式来指定加载的类型了,用起来很方便。另外需要注意,由于Class.forName()需要调用本地方法加载类,所以过程比较耗时,为了提高反射的性能,可以适当缓存这个获取到的对象。

3、Class对象存储在哪里?

虚拟机都会将类的对象存储在堆中,Class对象也不例外。

 

posted @ 2020-04-26 10:45  归去来兮辞  阅读(340)  评论(0编辑  收藏  举报