【黑马程序员】Java反射机制

 -------android培训java培训、期待与您交流! ----------

        Java程序中的许多对象在运行时都会出现两种类型,编译时类型和运行时类型:例如代码:Fruit f = new Apple();,这个代码会创建一个对象也就是变量,该变量的编译时类型为Fruit,运行时类型为Apple。还有一种情况,在程序调用某个方法的时候,需要传入一个外部对象,该对象的编译时类型为Object但在方法中又需要调用该对象运行时类型的方法。

        解决以上问题有两个方法:
  • 当对象运行时类型在编译时可以确定的,可是先使用instanceOf进行判断,然后强制转换。
  • 如果运行时类型不确定,则必须用到反射。
1. 反射的主要接口和类
        在 JDK 中,主要由以下类来实现 Java 反射机制,这些类都位于 java.lang.reflect 包中。
  •  Class 类:代表一个类;
  •  Field 类:代表类的成员变量(成员变量也称为类的属性);
  •  Method 类:代表类的方法;
  •  Constructor 类:代表类的构造方法;
  •  Array 类:提供了动态创建数组,以及访问数组元素的静态方法。
2. 反射的入口:Class 类
        Java 中的类反射就是一个在当前 JVM 中支持类、接口和对象内省的、小型的、类型安全的和可靠的 API。它是通过java.lang.reflect 包提供的, 可以对类的能力进行分析。反射使程序代码能够访问装载到 JVM 中的类的内部信息,主要包括获取已装载类的字段、方法和构造函数的信息, 允许编写处理类的代码。这些类是在程序运行时临时确定的,而非源代码中事先选定的,这使反射成为构建灵活应用的主要工具。
        1) Class类
        Java 中的“Class”类是 Java 中一个十分特殊的类。它和其它类一样继承自“Object”类, 其对象用以表达 Java程序运行时的类和接口。当一个类被加载时, JVM 便自动产生一个类对象。
        在 java.lang.Object 类中定义了 getClass()方法,因此对于任意一个 Java 对象,都可以通过此方法获得对象的类型。Class 类是 Reflection API 中的核心类,它有以下方法:
  •  getName():获得类的完整名字
  •  getFields():获得类的public类型的属性
  •  getDeclaredFields():获得类的所有属性
  •  getMethods():获得类的public类型的方法
  •  getDeclaredMethods():获得类的所有方法
  •  getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes参数指定方法的参数类型
  •  getConstrutors():获得类的public类型的构造方法
  •  getConstrutor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes参数指定构造方法的参数类型
  •  newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
        Class是Java反射机制的起源针对任何想探查的类,只有先为它产生一个Class对象, 接下来才能由这个Class对象调用为数十多个的反射APIs 因此,利用反射APIs动态访问某个类要经过以下三个步骤:
        ① 创建一个Class对象;
        ② 利用创建的Class对象获得类的内部信息;
        ③ 利用java.lang.reflect包的反射APIs,动态处理上一步获得的类的内部信息。
        
        2) Class类例
public static void main(String[] args) throws Exception {
    //取得某一个类的Class实例,包含着该类的元数据,有三种方法
    // Class<?> clazz = String.class; //方法1
    // Class clazz="abc".getClass(); //方法2
    Class clazz = Class.forName("java.lang.String");//方法3
    //从Class中取出对应类的全部字段,并输出其信息
    Field fields[] = clazz.getDeclaredFields();
    for (Field field : fields)
        System.out.println(field.getName() + field.getType());
    //从Class中取出对应类的某个字段,并输出其信息
    Field field = clazz.getDeclaredField("count");
    System.out.println(field.getName() + field.getType());
    //从Class中取出对应类的全部方法,并输出其信息
    Method methods[] = clazz.getDeclaredMethods();
    for (Method meth : methods)
        System.out.println(meth.getName()+meth.getReturnType());
    //从Class中取出对应类的某个方法,并输出其信息
    Method method = clazz.getMethod("hashCode", new Class[] {});
    System.out.println(method.getName() + method.getReturnType());
    //从Class中取出对应类的全部构造方法,并输出其信息
    Constructor constructors[] = clazz.getDeclaredConstructors();
    for (Constructor cons : constructors) {
        System.out.print(cons.getModifiers() + cons.getName());
        for (Type type : cons.getParameterTypes()) 
            System.out.println(type + " ");
    }
    // 从Class中取出对应类的某个构造方法,并输出其信息
    Constructor cons = clazz.getDeclaredConstructor(new Class[]{char[].class, int.class, int.class });
    System.out.print(cons.getModifiers() + cons.getName());
    for (Type type : cons.getParameterTypes())
        System.out.print(type + " ");
    // 从Class中取出对应类所在包类的完整名简名父类等信息
    System.out.println(clazz.getPackage().getName());
    System.out.println(clazz.getName());
    System.out.println(clazz.getSimpleName());
    System.out.println(clazz.getSuperclass());
}

        3) 取得Class实例的方法,见下表
        
3. 反射的具体使用
        Class对象可以获得该类里的成分包括构造器(由Constructor对象表示)方法(由Method对象表示)属性(由Field对象表示),这三个类都定义在java.lang.reflect包下,程序可以通过Constructor对象来调用对应的构造器创建对象实例,可以通过Method对象来执行对应的方法,通过Field对象直接访问或修改对象的属性值。
        
        1)运行时生成类的实例
        通过反射生成对象有如下两种方式:
        ① 通过Class的newInstance()方法
        使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方法要求该Class对象的对应类有无参构造方法,而执行newInstance()实际上就是执行无参构造方法来创建该类的实例
//相当于语句:Student stu=new Student();
Object obj = Class.forName("cn.bsw.reflection.Student").newInstance();
        此种方法比较常用,因为在很多JavaEE框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序就需要根据该字符串来创建对应的实例,此时必须使用反射
        ② 通过Constructor的newInstance()方法先使用Class对象获取指定的Constructor对象,在调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例通过这种方法可以选择使用某个类的指定构造方法来创建实例。
// 针对有参构造方法,相当于:Student stu = new Student ("zp",32,56.5f);
Class clazz = Class.forName("cn.bsw.reflection.Student");
Constructor cons = clazz.getConstructor(new Class[]{String.class,int.class,float.class});
Object obj = cons.newInstance(new Object[]{"zp",32,56.5f});
//针对无参构造方法,相当于语句:Student stu=new Student();
obj = clazz.getConstructor(new Class[]{}).newInstance(new Object[]{});
obj = clazz.getConstructor().newInstance();
System.out.println(obj);

        从以上代码中可以看出,采用第二种方法创建类的实例需要如下三个步骤:
        ① 获取该类的Class对象
        ② 利用Class对象的getConstructor()方法获取指定的构造器(准备一个Class[]作为该方法的参数类型)
        ③ 调用Constructor的newInstance()方法来创建相应类的对象(准备一个Object[]作为该方法的实参值)
        
        2)运行时变更与调用field值
        通过Class对象的getFields()或者getField()方法可以获得该类所包括的全部Field属性或指定Field属性Field提供了以下两组方法来访问属性getXxx(Object obj):获取obj对象该Field的属性值 此处的Xxx对应8个基本数据类型,如果该属性类型是引用类型则直接使用get(Object obj)
        setXxx(Object obj,Xxx val):将obj对象的该Field设置成val值此处的Xxx对应8个基本数据类型,如果该属性类型是引用类型则直接使用set(Object obj, Object val)setAccessible (Boolean flag):Field类从父类AccessibleObject中继承的方法 若flag为true,则取消属性的访问权限控制,即使private属性也可以进行访问;若flag为false,则取消属性的访问权限控制,private属性也可以进行访问
Class clazz = Class.forName("cn.bsw.reflection.Student");
Object obj = clazz.newInstance();
// 调用getDeclaredField("name")方法取得name属性对应的Field对象
Field f = clazz.getDeclaredField("name");
//取消属性的访问权限控制,即使private属性也可以进行访问
f.setAccessible(true);
//调用get()方法获取并输出对应属性值
System.out.println(f.get(obj));
//调用set()方法给对应属性赋值
f.set(obj, "zp");
//调用get()方法获取并输出对应属性修改后的值
System.out.println(f.get(obj));

        3)运行时调用method
        通过Class对象的getMethods()或者getMethod()方法可以获得该类所包括的全部方法或指定方法,这两个方法的返回值是Method[]或者Method每个Method对象对应一个方法,获得Method对象后,就可以调用它的invoke()方法来调用对应方法。
        Object invoke(Object obj,Object [] args):obj代表当前方法所属的对象的名字,args代表当前方法的参数列表,返回值Object是当前方法的返回值,即执行当前方法的结果。
        示例:
public class InvokeMethod {
    public int add(int x, int y) {
        return x + y;
    }
    public void shout(String name) {
        System.out.println("my name is" + name);
    }
    public static void main(String[] args) throws Exception {
        //创建该类的一个对象
        Class clazz = InvokeMethod.class;
        Object obj = clazz.newInstance();
        //调用该对象的add方法
        Method amethod = clazz.getMethod("add", new Class[]{int.class,int.class });
        Object result = amethod.invoke(obj, new Object[] {5, 7 });
        System.out.println(result);
        //调用该对象的shout方法
        Method smethod = clazz.getMethod("shout", new Class[] { String.class });
        smethod.invoke(obj, new Object[] {"zp" });
        //以上语句作用相当于直接执行如下语句
        // InvokeMethod im = new InvokeMethod();
        // int sum = im.add(5, 7);
        // System.out.println(sum);
        // im.shout("zp");
    }
}

        4)运行时动态创建数组并操作数组元素
        在java.lang.reflect 包下还提供了一个Array类,该类包括一系列static方法,通过这些方法可以来动态的创建数组,给数组元素赋值,取出数组元素值等Array提供的主要方法如下:
        static Object newInstance(Class<?> componentType, int[] dimensions): 创建一个具有指定的组件类型和维度的新数组。
static xxx getXxx(Object array, int index):以xxx形式返回指定数组对象array中第index个元素的值其中xxx代表八个基本数据类型如果元素类型是引用类型,则直接使用static Object get(Object array, int index)方法。
        static void setXxx(Object array, int index,xxx val):给数组对象array中第index个元素赋值val 其中xxx代表八个基本数据类型如果元素类型是引用类型,则直接使用static void set (Object array, int index,Objectval)方法。
        示例:
public class ArrayTest {
    public static void main(String[] args) throws Exception {
        //创建一个含有10个元素的整型数组
        Class clazz = Class.forName("java.lang.Integer");
        Object arr = Array.newInstance(clazz, 10);
        //给第五个元素赋值
        Array.set(arr, 5, 20);
        //取出第五个元素值并输出
        Object elem = Array.get(arr, 5);
        System.out.println(elem);
        //以上语句相当于执行如下语句
        // int arr[] = new int[10];
        // arr[5] = 20;
        // System.out.println(arr[5]);
    }
}
4. 反射的优点和缺点
        反射是提高Java程序的灵活性和可扩展性至关重要的技术,它使得Java软件系统具有自适应能力,实现程序的动态演进 它提供一种动态链接程序组件的多功能方法,允许程序创建和控制任何类的对象(根据安全性限制),无需提前硬编码目标类 这些特性使得反射特别适用于创建以非常普通方式与对象协作的库例如,反射经常在持续存储对象为数据库XML或其它外部格式的框架中使用 Java反射非常有用,如软件测试JavaBean EJB等,它使类和数据结构能按名称动态检索相关信息,并允许在运行着的程序中操作这些信息Java这一特性非常强大,并且是其它一些常用语言,如CC++Fortran等都不具备的充分发掘Java中的这一特性,可以编写出灵活低耦合高重用的组件目前许多流行的开源框架例如StrutsHibernateSpring在实现过程中都采用了Java反射技术
        但反射有两个缺点第一个是性能问题用于字段和方法接入时反射要远慢于直接代码使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求 这类操作总是慢于直接执行相同的操作因此Java反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用另外一个缺点是使用反射会模糊程序内部实际要发生的事情 程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术会带来维护问题反射代码比相应的直接代码更复杂解决这些问题的最佳方案是保守地使用反射 仅在它可以真正增加灵活性的地方记录其在目标类中的使用。    
5. 总结
        反射是提高Java程序的灵活性和可扩展性至关重要的技术,它使得Java软件系统具有自适应能力,实现程序的动态演进,是JAVA成为动态语言的一个重要特性理解Java反射技术的内部机制,对于我们理解目前流行的开源框架StrutsHibernateSpring等的实现机制是很有帮助的,同时在开发中应用Java反射技术,可以提高软件的灵活性重用性,降低耦合性。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值