肥宅兜

导航

Java 反射机制

基本概念

  在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?

  答案是肯定的。

  这种动态获取类的信息以及动态调用对象的方法的功能来自于Java语言的反射(Reflection)机制

  

  Java反射机制主要提供了以下功能:

  1.在运行时判断任意一个对象所属的类。

  2.在运行时构造任意一个类的对象。

  3.在运行时判断任意一个类所具有的成员变量和方法。

  4.在运行时调用任意一个对象的方法。

 

  Reflection是Java被视为动态(或准动态)语言的一个关键性质。

  这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息。

  包括其modifiers(诸如public、static等)、 superclass(例如Object)、实现了的 interfaces (例如Serializable)、也包括其fields和methods的所有信息,并可于运行时改变fields内容或调用methods。

 

动态语言

  动态语言的定义“程序运行时,允许改变程序结构或者变量类型,这种语言称为动态语言”。

  从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。

  尽管在这样的定义与分类下Java不是动态语言,它却有着一个非常突出的动态相关机制:Reflection。这个字的意思是:反射、映像、倒影,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。

  换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。

  这种“看透”class的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflectionintrospection是常被并提的两个术语。

 

Java Reflection API简介

  在JDK中,主要由以下类来实现Java反射机制,这些类(除了第一个)都位于java.lang.reflect包中

  Class类:代表一个类,位于java.lang包下。

  Field类:代表类的成员变量(成员变量也称为类的属性)。

  Method类:代表类的方法。

  Constructor类:代表类的构造方法。

  Array类:提供了动态创建数组,以及访问数组的元素的静态方法。

 

Class对象

  要想使用反射,首先需要获得待操作的类所对应的Class对象。

  Java中,无论生成某个类的多少个对象,这些对象都会对应于同一个Class对象。

  这个Class对象是由JVM生成的,通过它能够获悉整个类的结构。

 

  常用的获取Class对象的3种方式:

  1.使用Class类的静态方法。例如:  

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

 

  2.使用类的.class语法。如:

String.class;

 

  3.使用对象的getClass()方法。如:

String str = "aa";
Class<?> classType1 = str.getClass();

 

  getClass()方法定义在Object类中,不是静态方法,需要通过对象来调用,并且它声明为final,表明不能被子类所覆写。

  直接print所获得的Class对象classType会输出:

    class 完整类名

  如果调用该Class对象的getName()方法,则输出完整类名,不加class。

 

例程1:获取方法

  例程DumpMethods类演示了Reflection API的基本作用,它读取命令行参数指定的类名,然后打印这个类所具有的方法信息。 

import java.lang.reflect.Method;

public class DumpMethods
{
    public static void main(String[] args) throws Exception //在方法后加上这句,异常就消失了
    {
        //获得字符串所标识的类的class对象
        Class<?> classType = Class.forName("java.lang.String");//在此处传入字符串指定类名,所以参数获取可以是一个运行期的行为,可以用args[0]
        
        //返回class对象所对应的类或接口中,所声明的所有方法的数组(包括私有方法)
        Method[] methods = classType.getDeclaredMethods();
        
        //遍历输出所有方法声明
        for(Method method : methods)
        {
            System.out.println(method);
        }
    }

}

 

 

例程2:通过反射调用方法

  通过反射调用方法。详情见代码及注释:

 

import java.lang.reflect.Method;

public class InvokeTester
{
    public int add(int param1, int param2)
    {
        return param1 + param2;

    }

    public String echo(String message)
    {
        return "Hello: " + message;
    }

    public static void main(String[] args) throws Exception
    {

        // 以前的常规执行手段
        InvokeTester tester = new InvokeTester();
        System.out.println(tester.add(1, 2));
        System.out.println(tester.echo("Tom"));
        System.out.println("---------------------------");

        // 通过反射的方式

        // 第一步,获取Class对象
        // 前面用的方法是:Class.forName()方法获取
        // 这里用第二种方法,类名.class
        Class<?> classType = InvokeTester.class;

        // 生成新的对象:用newInstance()方法
        Object invokeTester = classType.newInstance();
        System.out.println(invokeTester instanceof InvokeTester); // 输出true

        // 通过反射调用方法
        // 首先需要获得与该方法对应的Method对象
        Method addMethod = classType.getMethod("add", new Class[] { int.class,
                int.class });
        // 第一个参数是方法名,第二个参数是这个方法所需要的参数的Class对象的数组

        // 调用目标方法
        Object result = addMethod.invoke(invokeTester, new Object[] { 1, 2 });
        System.out.println(result); // 此时result是Integer类型
        
        //调用第二个方法
        Method echoMethod = classType.getDeclaredMethod("echo", new Class[]{String.class});
        Object result2 = echoMethod.invoke(invokeTester, new Object[]{"Tom"});
        System.out.println(result2);

    }
}

 

 

生成对象

 

  若想通过类的不带参数的构造方法来生成对象,我们有两种方式:

  1.先获得Class对象,然后通过该Class对象的newInstance()方法直接生成即可:

     Class<?> classType = String.class;

     Object obj = classType.newInstance();

 

  2.先获得Class对象,然后通过该对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成

  (其中Customer是一个自定义的类,有一个无参数的构造方法,也有带参数的构造方法):

    Class<?> classType = Customer.class;

    // 获得Constructor对象,此处获取第一个无参数的构造方法的
    Constructor cons = classType.getConstructor(new Class[] {});

    // 通过构造方法来生成一个对象
    Object obj = cons.newInstance(new Object[] {});

 

  若想通过类的带参数的构造方法生成对象,只能使用下面这一种方式:

  (Customer为一个自定义的类,有无参数的构造方法,也有一个带参数的构造方法,传入字符串和整型)

    Class<?> classType = Customer.class;

    Constructor cons2 = classType.getConstructor(new Class[] {String.class, int.class});

    Object obj2 = cons2.newInstance(new Object[] {"ZhangSan",20});

  可以看出调用构造方法生成对象的方法和调用一般方法的类似,不同的是从Class对象获取Constructor对象时不需要指定名字,而获取Method对象时需要指定名字。

 

 

一个利用反射进行对象拷贝的例子

如下例程ReflectTester类进一步演示了Reflection API的基本使用方法。

  ReflectTester类有一个copy(Object object)方法,这个方法能够创建一个和参数object同样类型的对象,然后把object对象中的所有属性拷贝到新建的对象中,并将它返回。

  这个例子只能复制简单的类,假定类的每个属性都有public类型的getXXX()和setXXX()方法。

  

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectTester
{

    // 该方法实现对Customer对象的拷贝操作
    public Object copy(Object object) throws Exception
    {
        Class<?> classType = object.getClass();
        
        Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
        
        //获得对象的所有成员变量
        Field[] fields = classType.getDeclaredFields();
        for(Field field : fields)
        {
            //获取成员变量的名字
            String name = field.getName();    //获取成员变量的名字,此处为id,name,age

            //获取get和set方法的名字
            String firstLetter = name.substring(0,1).toUpperCase();    //将属性的首字母转换为大写            
            String getMethodName = "get" + firstLetter + name.substring(1);
            String setMethodName = "set" + firstLetter + name.substring(1);            

            //获取方法对象
            Method getMethod = classType.getMethod(getMethodName, new Class[]{});
            Method setMethod = classType.getMethod(setMethodName, new Class[]{field.getType()});//注意set方法需要传入参数类型
            
            //调用get方法获取旧的对象的值
            Object value = getMethod.invoke(object, new Object[]{});
            //调用set方法将这个值复制到新的对象中去
            setMethod.invoke(objectCopy, new Object[]{value});

        }

        return objectCopy;

    }

    public static void main(String[] args) throws Exception
    {
        Customer customer = new Customer("Tom",20);
        customer.setId(1L);
        ReflectTester tester = new ReflectTester();
        
        Customer customer2 = (Customer)tester.copy(customer);
        
        System.out.println(customer2.getId() + "," + customer2.getName() + "," + customer2.getAge());
        
    }
}

class Customer
{
    private long id;
    private String name;
    private int age;

    public Customer()
    {
    }

    public Customer(String name, int age)
    {
        this.name = name;
        this.age = age;
    }

    public long getId()
    {
        return id;
    }

    public void setId(long id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public int getAge()
    {
        return age;
    }

    public void setAge(int age)
    {
        this.age = age;
    }
}

 

反射与数组

java.lang.Array类提供了动态创建和访问数组元素的各种静态方法。

  例程ArrayTester1类的main()方法创建了一个长度为10的字符串数组,接着把索引位置为5的元素设为“hello”,然后再读取索引位置为5的元素的值。

  

import java.lang.reflect.Array;

public class ArrayTester1
{
    public static void main(String[] args) throws Exception
    {
        Class<?> classType = Class.forName("java.lang.String");
        
        //生成数组,指定元素类型和数组长度
        Object array = Array.newInstance(classType, 10);
        
        Array.set(array, 5, "hello");
        String str = (String)Array.get(array, 5);
        
        System.out.println(str);
                
    }

}

 

 

多维数组

      首先,区别一下下面两者:

 

  System.out.println(Integer.TYPE);

   System.out.println(Integer.class);

 

  输出:

  int

  class java.lang.Integer

 

  一个多维数组的程序实例:

  

import java.lang.reflect.Array;

public class ArrayTester2
{
    public static void main(String[] args)
    {
        int[] dims = new int[] { 5, 10, 15 };

        // 注意区分下面两种
        System.out.println(Integer.TYPE);    // int
        System.out.println(Integer.class);    // Integer

        // 创建一个三维数组,这个数组的三个维度分别是5,10,15
        Object array = Array.newInstance(Integer.TYPE, dims);
        // 可变参数,也可以这样写:
        // Object array = Array.newInstance(Integer.TYPE, 5,10,15);

        System.out.println(array instanceof int[][][]);
        
        Class<?> classType0 = array.getClass().getComponentType();    // 返回数组元素类型
        System.out.println(classType0);    // 三维数组的元素为二维数组,输出:class [[I

        // 获得第一层的索引为3的数组,返回的是一个二维数组
        Object arrayObject = Array.get(array, 3);
        Class<?> classType = arrayObject.getClass().getComponentType();    // 返回数组元素类型
        System.out.println(classType);    // 二维数组的元素为一维数组,输出:class [I

        // 此处返回的是一个一维数组
        arrayObject = Array.get(arrayObject, 5);
        Class<?> classType2 = arrayObject.getClass().getComponentType();    // 返回数组元素类型
        System.out.println(classType2);    // 一维数组的元素为int

        // 给一维数组下标为10的位置设置值为37
        Array.setInt(arrayObject, 10, 37);

        int[][][] arrayCast = (int[][][]) array;
        System.out.println(arrayCast[3][5][10]);
    }

}

 

 

 

利用反射调用私有方法、访问私有属性

 

  利用反射,首先是Class对象的获取,之后是Method和Field对象的获取。

  以Method为例,从文档中可以看到:

  getMethod()方法返回的是public的Method对象,

  而getDeclaredMethod()返回的Method对象可以是非public的。

  Field的方法同理。

 

  访问私有属性和方法,在使用前要通过AccessibleObject类(Constructor、 Field和Method类的基类)中的setAccessible()方法来抑制Java访问权限的检查

 

实例1,调用私有方法

  假设有这样一个类,其中包含私有方法。

public class PrivateClass
{
    private String sayHello(String name)
    {
        return "Hello: " + name;
    }

}

  利用反射机制在外部访问该方法: 

import java.lang.reflect.Method;

public class TestPrivate
{

    public static void main(String[] args) throws Exception
    {
        PrivateClass p = new PrivateClass();

        Class<?> classType = p.getClass();

        // 获取Method对象
        Method method = classType.getDeclaredMethod("sayHello",
                new Class[] { String.class });

        method.setAccessible(true); // 抑制Java的访问控制检查
        // 如果不加上上面这句,将会Error: TestPrivate can not access a member of class PrivateClass with modifiers "private"
        String str = (String) method.invoke(p, new Object[] { "zhangsan" });

        System.out.println(str);
    }
}


 

实例2,访问私有属性

  直接访问私有属性,将例子中的私有属性改值。

  一个包含私有属性的类:

public class PrivateClass2
{
    private String name = "zhangsan";

    public String getName()
    {
        return name;
    }
}

  利用反射修改其私有属性的值:  

import java.lang.reflect.Field;

public class TestPrivate2
{
    public static void main(String[] args) throws Exception
    {
        PrivateClass2 p = new PrivateClass2();
        Class<?> classType = p.getClass();

        Field field = classType.getDeclaredField("name");

        field.setAccessible(true); // 抑制Java对修饰符的检查
        field.set(p, "lisi");

        System.out.println(p.getName());
    }

}

 

 

Class类

  Java中的Object类,是所有类的继承根源,其中的getClass()方法返回一个Class Object。所有的类都有这个方法。

  Class类十分特殊,它和其他类一样继承自Object,其实体用以表达Java程序运行时的classes和interfaces,也用来表达enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及关键词void。

  当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM便自动产生一个Class对象(Class object)。

  如果您想借由修改Java标准库源码来观察Class对象的实际生成时机,例如在Class的constructor内添加println(),那是不可以的。因为Class没有public constructor。

  Class是Reflection起源。针对任何您想探勘的类,唯有先为它产生一个Class object,接下来才能经由后者唤起为数十多个的Reflection APIs。

 

“Class”object的取得途径

  1.运用getClass()方法,返回Class对象。

  运用Class.getSuperclass()可以得到父类的Class对象,如果是Object类则返回null。

  2.运用静态方法Class.forName()

  3.运用.class语法。类名.class

  其中,还可以通过int[].class的形式获得整形数组的Class对象。

  包装类的.TYPE语法实际返回的是所对应的原生数据类型的Class对象。

 

posted on 2016-08-29 21:52  肥宅兜  阅读(511)  评论(0编辑  收藏  举报