Java反射机制

来源:互联网 发布:java发送短信 编辑:程序博客网 时间:2024/05/21 17:27


一、什么是反射机制?为什么要用反射机制?

所谓Java反射机制是指,程序在运行状态时,可以加载一个运行时才得知名称的class,能够知道这个类的所有属性和方法,并生成其对象实体、或对其fields设值、或调用其方法;即利用反射技术,根据一个类名称,可以得到该类的构造方法、属性、方法等信息,并创建其对象。用一句话来概括,反射就是加载一个运行时才知道的类以及它的完整内部结构。 

那我们为什么要用反射机制呢?

第一,反射的目的就是为了扩展未知的应用。比如,我们写好了一个软件,其中定义了一些接口,程序已经过编译并且发布了,当我们以后需要扩展功能时,不可能去修改已经安装在别人机器上的软件源码,此时我们只需要另写一个插件,让其实现某些接口即可,程序运行时,通过反射技术动态的创建和编译新写的类,并获知其内部细节,就可以调用其方法了;

第二,在编码阶段不知道那个类名,要在运行期从配置文件读取类名, 这时候就没有办法以new的方式硬编码,而必须用到反射才能创建这个对象。 

二、如何使用反射?

1. 反射中的类

Java反射机制的实现要借助于4个类:ClassConstructorFieldMethod,其中,

Class——类对象(Class 类的实例表示正在运行的 Java 应用程序中的类和接口 

Constructor——类的构造器对象

Field——类的属性对象

Method——类的方法对象

可以看到一个类的各个组成部分(构造方法、属性、方法)都被封装成一个单独的类。

而java.lang.Class 提供了四种独立的反射调用,以不同的方式来获得以上信息。

获取构造方法的反射调用:

Constructor getConstructor(Class[] params) -- 匹配给定的参数类型来得到相应的公共构造函数  

Constructor[] getConstructors() -- 获得类的所有公共构造函数  

Constructor getDeclaredConstructor(Class[] params) -- 匹配给定的参数类型来得到相应 的构造函数 (从publicprivateprotect、默认中匹配选择

Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数

获取字段属性的反射调用:

Field getField(String name) -- 获得匹配给定名称参数的公共字段  

Field[] getFields() -- 获得类的所有公共字段  

Field getDeclaredField(String name) -- 获得匹配给定名称参数的字段(从publicprivateprotect、默认中匹配选择  

Field[] getDeclaredFields() -- 获得类声明的所有字段 

获取方法的反射调用:

Method getMethod(String name, Class[] params) -- 匹配给定的方法参数名name和参数 类型param公共方法  

Method[] getMethods() -- 获得类的所有公共方法  

Method getDeclaredMethod(String name, Class[] params) -- 匹配给定的方法参数名 name和参数类型param的方法(从publicprivateprotect、默认中匹配选择

Method[] getDeclaredMethods() -- 获得类声明的所有方法

 

2. 反射的使用步骤

步骤一:获取类的Class对象和创建类的实例对象

· 获取类的Class对象:

① 通过Class.forName(String className) ——className表示完整的类路径

② 运用.class语法,如String.class

③ 调用ObjectgetClass()方法,如:

String str = "abc";

Class c = str.getClass();

④ 对于基本数据类型,可以使用其包装类中与定义好的TYPE字段,如

Class c = Integer.TYPE;

其中,以“.class”来创建Class对象的引用时,不会自动地初始化该Class对象,而使用Class.forName()立即就进行了初始化。例如,如果一个static final属性值是“编译期常量”(public static final int A = 2;),那么这个值不需要对类进行初始化就可以被读取。

注:获取Class的引用,其实就是在加载或寻找编译期编译出来的“.class”字节码文件。

附(

Class.forName() 和 ClassLoader.loadClass()的区别

Class.forName("xx")等同于 Class.forName("xx",true,CALLClass.class.getClassLoader()),第二个参数(bool)表示 装载类的时候是否初始化该类,即调用类的静态块的语句及初始化静态成员变量并为其分配空间
ClassLoader loader = Thread.currentThread.getContextClassLoader(); //也可以用 (ClassLoader.getSystemClassLoader())

Class cls = loader.loadClass("xx"); //这句话没有执行初始化,其实与 Class.forName("xx.xx"falseloader)是一致的,只是loader.loadClass("xx")执行的 是更底层的操作。
只有执行cls.NewInstance()才能够初始化类,得到该类的一个实例 

· 创建类的实例对象,如:

Class c = Class.forName("com.ReflectionTest.");

Object obj = c.newInstance();

步骤二:调用诸如getDeclaredMethods()的方法,已取得类的构造方法、属性或者方法

例如:

Class c = Class.forName("java.lang.String");

Method m[] = c.getDeclaredMethods(); 

步骤三:对于第二步骤取得的属性、方法、构造方法等采用Reflection API进行处理

下面引用一个例子:

 /**
    * 通过java的反射机制动态修改对象的属性
    * @param o
    */
    public void test2(Customer o) {
        try {
            Class c = o.getClass();
            //getMethod方法第一个参数指定一个需要调用的方法名称,第二个参数是需要调用方法的参数类型列表,如无参数可以指定null,该方法返回一个方法对象 
            Method sAge = c.getMethod("setAge", new Class[] { int.class });
            Method gAge = c.getMethod("getAge", null);
            Method sName = c.getMethod("setName", new Class[] { String.class });
            //动态修改Customer对象的age
            Object[] args1 = { new Integer(25) };
            sAge.invoke(o, args1);
            //动态取得Customer对象的age
            Integer AGE = (Integer) gAge.invoke(o, null);
            System.out.println("the Customer age is: " + AGE.intValue());
            //动态修改Customer对象的name
            Object[] args2 = { new String("李四") };
            sName.invoke(o, args2);

        } catch (Throwable e) {
            System.err.println(e);
        }
    }

3.反射机制的功能?

① 在运行时查询并调用任意一个类所具有的成员变量和方法;

② 在运行时判断任意一个对象所属的类。例如:

String str = new String("abc");

Class c = str.getClass();

Boolean flag = c.isInstance(new String()) 

③ 在运行时创建类的实例;

·运行时创建实例对象

有两种办法生成实例对象:

针对无参数的构造方法,可以使用Class类里的newInstance()方法,该newInstance()方法不带参。例如:

Class c = String.class;

Object obj = c.newInstance();

要调用有参数的构造方法,就需要通过Constructor类的newInstance(Object ... obj)方法带参。例如:

//获得StringClass实例

Class clazz=String.class;    

//创建一个数组,这个数组用来放要实例化对象的构造器里的参数类型    

Class[] param=new Class[1];

//放入构造器中的参数类型,如果有多个,按顺序放入   

param[0]=String.class;    

//实例化一个构造器对象,并把放着构造器参数类型的数组作为参数传进去    

Constructor constructor=clazz.getConstructor(param);

//创建一个Object数组,用于放构造器中对应的值    

Object[] obj=new Object[1];    

//将值放入到数组中,这里要注意,param数组中放入的是什么类型,这里就要按顺序放入    

obj[0]="zhang3";    

//实例化对象,并把放着构造器要传入的参数的数组传到方法中    

String str=(String)constructor.newInstance(obj)

这样,我们就通过java.lang.reflect.Constructor实例化出来了String类型的对象。

·newInstance()new关键字创建实例的比较

在初始化一个类,生成一个实例的时候,newInstance()方法和new关键字除了一个 是方法,一个是关键字外,最主要的区别在于创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类。那么为什么会有两种创建对象方式?这主要考虑到软件的可伸缩、可扩展和可重用等软件设计思想。

JVM的角度看,我们使用关键字new创建一个类的时候,这个类可以没有被加载。但是使用newInstance()方法的时候,就必须保证:

1、这个类已经加载;

2、这个类已经链接了(即为静态域分配存储空间,并且如果必须的话将解析这个类创建的对其他类的所有引用)。而完成上面两个步骤的正是Class的静态方法forName()所完成的,这个静态方法调用了启动类加载器,即加载javaAPI的那个加载器。

可以看出,newInstance()实际上是把new这个方式分解为两步,即首先调用Class加载方法加载某个类,然后实例化。+这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了一种降耦的手段

最后用最简单的描述来区分new关键字和newInstance()方法的区别:  newInstance:弱类型。低效率。只能调用无参构造方法

New:强类型。相对高效。能调用任何public构造。

jvm会执行静态代码段,只要记住一个概念,静态代码是和class绑定的,class装载成功就表示执行了你的静态代码了。而且以后不会再走这段静态代码了。

④ 当要调用的一个类的方法为private时,可以采用反射技术调用;

先根据完整类名加载该类——>利用newInstanceConstructor实例化——>通过getDeclaredMethods()得到私有的方法——>取消Java语言访问检查以访问private方法(即调用setAccessible(true)  ——>最后通过Methodinvoke方法调用该私有方法。

⑤ 利用反射实现类的动态加载(实现java动态绑定的前提

多态是Java面向对象语言的一个重要特性,而向上转型是实现多态的重要一步,对于面向接口或父类的引用,JVM是靠后期绑定(动态绑定)的机制进行对象类型的判定的,即在运行时才能确定调用哪些代码。而实现动态绑定的基础便是反射机制的应用,通过反射在运行时根据类名得到该类的具体信息。

三、我们学习过的知识中哪些用了反射机制?

工厂模式、SpringIOC(或DI)、AOP都使用到了java反射机制


原创粉丝点击