Java反射机制(四)—番外篇,实例化方法深入

    反射机制这几篇博客写下来发现涉及到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加载类的过程和不同方式之间的区别。其实际上只是封装的程度不一样,也就是方法的粒度的差别。当然,有一点内容还没有通过自己的测试得到验证,可能是我的方法不对或者是资料有问题。权且记下这个问题!下篇博客再见!




         

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。