zoukankan      html  css  js  c++  java
  • 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对象。

  • 相关阅读:
    jenkins GitHub 自动触发
    rabbitmq web管理
    系统编码,文件编码,python编码
    反转二叉树
    从右边看二叉树
    python pyenv
    js 闭包
    git review & devops过程
    centos7搭建自己的yum源
    优先级队列PriorityQueue 测试,会自动排序
  • 原文地址:https://www.cnblogs.com/doudouxiaoye/p/5819813.html
Copyright © 2011-2022 走看看