反射机制这几篇博客写下来发现涉及到Java类的加载机制,这部分的内容也比较独立的一部分,因此单另一篇来写。在JAVA中任何的类都是需要加载到JVM中才能运行的。之前Class Loader介绍了类的加载机制,那么这里要说的是不同加载方式之间的对比,好能对JAVA类的实例化过程有更深刻的体会。
new和Class.newInstance
我们说代码里出现new关键字意味着对于可能变动的代码,耦合过高了。遇到这种情况我们会用反射机制来去除new关键字,这在代理模式里我们见过了。实际上也就是用了Class.newInstance来代替。这说明这两种方式都可以得到相同的对象实例,但是它们之间存在区别,耦合度不同。
实际上在理解上我们可以认为,Class.newInstanc方式来实例化对象是对new关键字的拆分成两步了。因为,Class.newInstance的使用是有前提的,要保证类已经加载到JVM中,并且已经链接。看如下代码:
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
//从当前线程取得正在运行的加载器
ClassLoader cl=Thread.currentThread().getContextClassLoader();
cl.loadClass("com.zjj.ClassTest.Test"); //加载测试类到JVM
Class c2=cl.getClass(); //得到类的Class对象
c2.newInstance(); //实例化对象
}
}</span></span>
这里不用Class.forName来得到Class对象是为了保证类被加载了但是没有被链接。 这段代码看着貌似没什么错,编译也没有问题,但是运行的时候就出错了。也就是说通过如上方法加载的类是没有被链接的,因此newInstance方法无法执行。
前面说理解上可以简单的认为是通过Class.Instance方式是new拆分的两步,但是事实上new要比Class.Instance做的多。Class.Instance方法只能访问无参数的构造函数,new则都可以访问。建立一个有两个构造函数的测试类,看客户端调用代码:
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
Class c=Class.forName("com.zjj.ClassTest.Test");
c.newInstance();
new Test("ni");
}
}</span></span>
输出结果为:
无参数的构造函数
带参数的构造函数
如果在newInstance中传入参数去调用带参数的构造函数的话是会报错的,无法通过编译。相对来说newInstance是弱类型,new是强类型。
Class.forName和classLoad.loadClass
讲这两个的区别之前我们先要了解,JVM会执行静态代码段,要记住一个概念,静态代码是和class绑定的,class装载成功就表示执行了静态代码了,以后也就不会再走这段静态代码了。 也就是说静态代码段是只会执行一次的,在类被加载的时候。另外我们还需要知道,类的加载过程分为装载、连接、初始化。还有就是,JVM遇到类请求时它会先检查内存中是否存在,如果不存在则去加载,存在则返回已存在的Class对象。
那么这两个方法的区别就在于执行的这三个过程不一样。forName有两个函数(多态),三个参数时forName(String className, boolean initialize, ClassLoader loader)第二个参数为True时则类会链接,会初始化。为False时,如果原来不存在则一定不会连接和初始化,如果原来存在被连接的Class对象,则返回该对象但是依然不会初始化。单参数时,默认initialize是为True的。
loadClass也是多态loadClass(String name)单参数时, resolve=false。如果该类已经被该类装载器所装载,那么,返回这个已经被装载的类型的Class的实例,否则,就用这个自定义的类装载器来装载这个class,这时不知道是否被连接。绝对不会被初始化!这时唯一可以保证的是,这个类被装载了。但是不知道这个类是不是被连接和初始化了。
loadClass(String name, boolean resolve)resolve=true时,则保证已经装载,而且已经连接了。 resolve=falses时,则仅仅是去装载这个类,不关心是否连接了,所以此时可能被连接了,也可能没有被连接。下面通过测试来验证以上说的内容,代码如下:
Test类:
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public class Test {
static {
System.out.println("静态初始化");
}
public Test(){
System.out.println("无参数的构造函数");
}
public Test(String str){
System.out.println("带参数的构造函数");
}
{
System.out.println("非静态初始化");
}
}</span></span>
测试一:客户端调用代码
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
Class c=Class.forName("com.zjj.ClassTest.Test");
}
}</span></span>
输出结果为:静态初始化
说明:Class.forName时类执行了装载、连接、初始化三个步骤。
测试二:客户端代码改为
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
ClassLoader cl=Thread.currentThread().getContextClassLoader();
Class c=Class.forName("com.zjj.ClassTest.Test", false, cl);
}
}</span></span>
输出结果为:initialize=true时输出,静态初始化。为false时没有输出
说明:为true时类执行了装载、连接、初始化三个步骤。为false时没有初始化,为知是不是连接。
测试三:客户端代码改为
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
ClassLoader cl=Thread.currentThread().getContextClassLoader();
Class c=Class.forName("com.zjj.ClassTest.Test", false, cl);
c.newInstance();
}
}</span></span>
输出结果为:
静态初始化
非静态初始化
无参数的构造函数
说明:为了保证JVM中不存在之前加载过的类,特地清理了JVM内存。但是输出结果不变,说明为false时执行了装载和链接,否则newInstance是无法执行的(前面说过了newInstance的执行条件)。但是资料说可能还存在不连接的情况!!有待考证。
测试四:客户端代码改为
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
Class c=Class.forName("com.zjj.ClassTest.Test");
ClassLoader cl=Thread.currentThread().getContextClassLoader();
Class c=Class.forName("com.zjj.ClassTest.Test", true, cl);
}
}</span></span>
输出结果为:静态初始化
说明:如果原来存在加载过的类,那么第二次执行加载请求时返回存在的。因为,静态初始化只执行了一次。
测试五:客户端代码改为
<span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public static void main(String[] arg) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
//从当前线程取得正在运行的加载器
ClassLoader cl=Thread.currentThread().getContextClassLoader();
cl.loadClass("com.zjj.ClassTest.Test"); //加载测试类到JVM
Class c2=cl.loadClass("com.zjj.ClassTest.Test").getClass(); //得到类的Class对象
c2.newInstance(); //实例化对象
}
}</span></span>
输出结果:报错
说明:此时loadClass方法加载到内存中的类是未连接的,当然不会初始化。因此也就没有“静态初始化”的输出。
测试六:不知道为什么没有发现代码中的ClassLoader存在两个参数的loadClass方法。
总结:至此方法对比结束,这篇博客主要是更细致的了解了JVM加载类的过程和不同方式之间的区别。其实际上只是封装的程度不一样,也就是方法的粒度的差别。当然,有一点内容还没有通过自己的测试得到验证,可能是我的方法不对或者是资料有问题。权且记下这个问题!下篇博客再见!