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

反射机制这几篇博客写下来发现涉及到Java类的加载机制,这部分的内容也比较独立的一部分,因此单另一篇来写。在JAVA中任何的类都是需要加载到JVM中才能运行的。之前Class Loader介绍了类的加载机制,那么这里要说的是不同加载方式之间的对比,好能对JAVA类的实例化过程有更深刻的体会。 

new和Class.newInstance

    我们说代码里出现new关键字意味着对于可能变动的代码,耦合过高了。遇到这种情况我们会用反射机制来去除new关键字,这在代理模式里我们见过了。实际上也就是用了Class.newInstance来代替。这说明这两种方式都可以得到相同的对象实例,但是它们之间存在区别,耦合度不同。
    实际上在理解上我们可以认为,Class.newInstanc方式来实例化对象是对new关键字的拆分成两步了。因为,Class.newInstance的使用是有前提的,要保证类已经加载到JVM中,并且已经链接。看如下代码:
  1. <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{ 
  2.         //从当前线程取得正在运行的加载器 
  3.         ClassLoader cl=Thread.currentThread().getContextClassLoader(); 
  4.         cl.loadClass("com.zjj.ClassTest.Test");    //加载测试类到JVM 
  5.         Class c2=cl.getClass();         //得到类的Class对象 
  6.         c2.newInstance();               //实例化对象      
  7.     } 
  8. }</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.getClass();         //得到类的Class对象
    	c2.newInstance();               //实例化对象    	
    }
}</span></span>
    这里不用Class.forName来得到Class对象是为了保证类被加载了但是没有被链接。 这段代码看着貌似没什么错,编译也没有问题,但是运行的时候就出错了。也就是说通过如上方法加载的类是没有被链接的,因此newInstance方法无法执行。
    前面说理解上可以简单的认为是通过Class.Instance方式是new拆分的两步,但是事实上new要比Class.Instance做的多。Class.Instance方法只能访问无参数的构造函数,new则都可以访问。建立一个有两个构造函数的测试类,看客户端调用代码:
  1. <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{ 
  2.         Class c=Class.forName("com.zjj.ClassTest.Test"); 
  3.        c.newInstance();          
  4.         new Test("ni");      
  5.     } 
  6. }</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");
       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类:
  1. <span style="font-family:FangSong_GB2312;font-size:18px;"><span style="font-family:FangSong_GB2312;">public class Test { 
  2.    static
  3.        System.out.println("静态初始化"); 
  4.    }    
  5.    public Test(){ 
  6.        System.out.println("无参数的构造函数"); 
  7.    } 
  8.    public Test(String str){ 
  9.        System.out.println("带参数的构造函数"); 
  10.    } 
  11.    { 
  12.        System.out.println("非静态初始化"); 
  13.    } 
  14. }</span></span> 
<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>
   测试一:客户端调用代码
  1. <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{ 
  2.         Class c=Class.forName("com.zjj.ClassTest.Test"); 
  3.      } 
  4. }</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时类执行了装载、连接、初始化三个步骤。
    测试二:客户端代码改为
  1. <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{ 
  2.         ClassLoader cl=Thread.currentThread().getContextClassLoader(); 
  3.         Class c=Class.forName("com.zjj.ClassTest.Test", false, cl); 
  4.      } 
  5. }</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();
    	Class c=Class.forName("com.zjj.ClassTest.Test", false, cl);
     }
}</span></span>
    输出结果为:initialize=true时输出,静态初始化。为false时没有输出
    说明:为true时类执行了装载、连接、初始化三个步骤。为false时没有初始化,为知是不是连接。
    测试三:客户端代码改为
  1. <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{ 
  2.         ClassLoader cl=Thread.currentThread().getContextClassLoader(); 
  3.         Class c=Class.forName("com.zjj.ClassTest.Test", false, cl); 
  4.                c.newInstance(); 
  5.      } 
  6. }</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();
    	Class c=Class.forName("com.zjj.ClassTest.Test", false, cl);
               c.newInstance();
     }
}</span></span>
    输出结果为:
        静态初始化
        非静态初始化
        无参数的构造函数
    说明:为了保证JVM中不存在之前加载过的类,特地清理了JVM内存。但是输出结果不变,说明为false时执行了装载和链接,否则newInstance是无法执行的(前面说过了newInstance的执行条件)。但是资料说可能还存在不连接的情况!!有待考证。
    测试四:客户端代码改为
  1. <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{ 
  2.                Class c=Class.forName("com.zjj.ClassTest.Test"); 
  3.         ClassLoader cl=Thread.currentThread().getContextClassLoader(); 
  4.         Class c=Class.forName("com.zjj.ClassTest.Test", true, cl); 
  5.              } 
  6. }</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");
    	ClassLoader cl=Thread.currentThread().getContextClassLoader();
    	Class c=Class.forName("com.zjj.ClassTest.Test", true, cl);
             }
}</span></span>
    输出结果为:静态初始化
    说明:如果原来存在加载过的类,那么第二次执行加载请求时返回存在的。因为,静态初始化只执行了一次。
    测试五:客户端代码改为
  1. <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{               
  2.                //从当前线程取得正在运行的加载器 
  3.         ClassLoader cl=Thread.currentThread().getContextClassLoader(); 
  4.         cl.loadClass("com.zjj.ClassTest.Test");    //加载测试类到JVM 
  5.         Class c2=cl.loadClass("com.zjj.ClassTest.Test").getClass();         //得到类的Class对象 
  6.         c2.newInstance();               //实例化对象 
  7.              } 
  8. }</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加载类的过程和不同方式之间的区别。其实际上只是封装的程度不一样,也就是方法的粒度的差别。当然,有一点内容还没有通过自己的测试得到验证,可能是我的方法不对或者是资料有问题。权且记下这个问题!下篇博客再见!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java反射机制是在运行时动态地获取类的信息,包括类名、方法名、属性等,并且可以在运行时调用对象的方法和访问对象的属性。下面是一个简单的Java反射机制的实例: 假设我们有一个Person类,其中包含以下属性和方法: ```java public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public void sayHello() { System.out.println("Hello, my name is " + name + ", and I am " + age + " years old."); } } ``` 现在我们可以使用反射机制来获取Person类的信息并创建它的实例: ```java import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class ReflectionExample { public static void main(String[] args) throws Exception { // 获取Person类的Class对象 Class<Person> personClass = Person.class; // 获取Person类的构造函数 Constructor<Person> constructor = personClass.getConstructor(String.class, int.class); // 创建Person类的实例 Person person = constructor.newInstance("John", 30); // 获取Person类的name属性 Field nameField = personClass.getDeclaredField("name"); nameField.setAccessible(true); // 修改Person类的name属性 nameField.set(person, "Tom"); // 调用Person类的sayHello方法 Method sayHelloMethod = personClass.getDeclaredMethod("sayHello"); sayHelloMethod.invoke(person); } } ``` 在这个例子中,我们首先使用`Person.class`获取Person类的Class对象,然后使用`getConstructor`方法获取构造函数,使用`newInstance`方法创建Person类的实例。 接下来,我们使用`getDeclaredField`方法获取Person类的name属性,并使用`setAccessible`方法设置该属性可访问。然后,我们使用`set`方法修改该属性的值。 最后,我们使用`getDeclaredMethod`方法获取Person类的sayHello方法,并使用`invoke`方法调用该方法。 这个例子只是反射机制的一个简单示例,反射机制还可以应用于许多其他方面,例如动态代理、注解处理等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值