下面介绍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()需要调用本地方法加载类,所以过程比较耗时,为了提高反射的性能,可以适当缓存这个获取到的对象。