Java反射

389 阅读8分钟

1、反射概述

1.1、反射介绍

  • JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
  • 对于任意一个对象,都能够调用它的任意一个方法和属性;
  • 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制;
  • 要想解剖一个类,必须先要获取到该类的字节码文件对象;
  • 而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。

2、获取class文件对象的三种方式

2.1、第一种方法【Object类的getClass()方法】

  • 在内存中新建一个Person的实例,对象p对这个内存地址进行引用

  • 对象p调用getClass()返回对象p所对应的Class

  • 调用newInstance()方法让Class对象在内存中创建对应的实例,并且让p2引用实例的内存地址

    Person p = new Person();
    Class<?> cls=p.getClass();
    Person p2=(Person)cls.newInstance();
    

2.2、第二种方法【静态属性class】

  • 获取指定类型的Class对象,这里是Person

  • 调用newInstance()方法在让Class对象在内存中创建对应的实例,并且让p引用实例的内存地址

    Class<?> cls=Person.Class();
    Person p=(Person)cls.newInstance();
    

2.3、第三种方法【Class类中静态方法forName()】

  • 通过JVM查找并加载指定的类(下面的代码指定加载了com.boxiaoyuan包中的Person类)

  • 调用newInstance()方法让加载完的类在内存中创建对应的实例,并把实例赋值给p

  • 注意:如果找不到时,它会抛出 ClassNotFoundException 这个异常,这个很好理解,因为如果查找的类没有在 JVM 中加载的话,自然要告诉开发者。

    Class<?> cls=Class.forName("com.boxiaoyuan.Person"); //forName(包名.类名)
    Person p= (Person) cls.newInstance();
    

3、Class类的主要方法

getName():获得类的完整名字。
getFields():获得类的public类型的属性。
getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
getMethods():获得类的public类型的方法。
getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstructors():获得类的public类型的构造方法。
getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
newInstance():通过类的构造方法创建这个类的一个对象。

4、反射的定义

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制

5、反射的组成

由于反射最终也必须有类参与,因此反射的组成一般有下面几个方面组成:

  • 1.java.lang.Class.java:类对象;
  • 2.java.lang.reflect.Constructor.java:类的构造器对象;
  • 3.java.lang.reflect.Method.java:类的方法对象;
  • 4.java.lang.reflect.Field.java:类的属性对象;

反射中类的加载过程

  • 根据虚拟机的工作原理,一般情况下,类需要经过:加载->验证->准备->解析->初始化->使用->卸载这个过程,如果需要反射的类没有在内存中,那么首先会经过加载这个过程,并在在内存中生成一个class对象,有了这个class对象的引用,就可以发挥开发者的想象力,做自己想做的事情了。

6、反射的作用有哪些

前面只是说了反射是一种具有与Java类进行动态交互能力的一种机制,一般情况下下面几种场景会用到反射机制.

  • 需要访问隐藏属性或者调用方法改变程序原来的逻辑,这个在开发中很常见的,由于一些原因,系统并没有开放一些接口出来,这个时候利用反射是一个有效的解决方法
  • 自定义注解,注解就是在运行时利用反射机制来获取的。

7、反射的相关使用

7.1、通过反射获取成员变量(包含私有)并使用

public class Demo01Reflect {
    /*
    * Class.getField(String)方法可以获取类中的指定字段(可见的),
    * 如果是私有的可以用getDeclaedField("name")方法获取,通过set(obj, "李四")
    * 方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)设置访问权限,
    * 用获取的指定的字段调用get(obj)可以获取指定对象中该字段的值
    */
    public static void main(String[] args) throws Exception {
        Class aClass = Class.forName("com.chuxuezhe.relfect.Person");
        Constructor constructor = aClass.getConstructor(String.class, int.class);
        Person person = (Person) constructor.newInstance("张三", 22);
        //如果是私有的可以用getDeclaedField("name")方法获取
        //如果是私有的需要先调用setAccessible(true)设置访问权限,
        Field field = aClass.getDeclaredField("name");
        field.setAccessible(true);
        field.set(person, "李四");
        person.say();
    }
}

7.2、通过反射获取无参无返回值成员方法(包含私有)并使用

public class Demo01Reflect {
    public static void main(String[] args) throws Exception {
        //获取字节码文件对象
        Class aClass = Class.forName("com.chuxuezhe.relfect.Person");
        //调有参构造方法并创建对象
        Constructor constructor = aClass.getConstructor(String.class, int.class);
        Person person = (Person) constructor.newInstance("张三", 19);
        //第一个参数表示方法名,第二个参数表示的是方法的参数类型.class
        Method method = aClass.getMethod("say");
        //第一个参数表示对象是谁,第二个参数表示调用该方法的实际参数
        method.invoke(person);
    }
}

7.3、通过反射获取带参无返回值成员方法并使用

public class Demo01Reflect {
    public static void main(String[] args) throws Exception {
        //获取字节码文件对象
        Class aClass = Class.forName("com.chuxuezhe.relfect.Person");
        //调有参构造方法并创建对象
        Constructor constructor = aClass.getConstructor(String.class, int.class);
        Person person = (Person) constructor.newInstance("张三", 19);
        //第一个参数表示方法名,第二个参数表示的是方法的参数类型.class
        Method method = aClass.getMethod("printAgeByName", String.class);
        //第一个参数表示对象是谁,第二个参数表示调用该方法的实际参数
        method.invoke(person, "张三");
    }
}

7.4、通过反射获取带参带返回值成员方法并使用

public class Demo01Reflect {
    public static void main(String[] args) throws Exception {
        //获取字节码文件对象
        Class aClass = Class.forName("com.chuxuezhe.relfect.Person");
        //调有参构造方法并创建对象
        Constructor constructor = aClass.getConstructor(String.class, int.class);
        Person person = (Person) constructor.newInstance("张三", 19);
        //第一个参数表示方法名,第二个参数表示的是方法的参数类型.class
        Method method = aClass.getMethod("getAgeByName", String.class);
        //第一个参数表示对象是谁,第二个参数表示调用该方法的实际参数
        String str = (String)method.invoke(person, "王五");
        System.out.println(str);
    }
}

8、相关知识点

8.1、设置.setAccessible(true)暴力访问权限

一般情况下,我们并不能对类的私有字段进行操作,利用反射也不例外,但有的时候,例如要序列化的时候,我们又必须有能力去处理这些字段,这时候,我们就需要调用AccessibleObject上的setAccessible()方法来允许这种访问,而由于反射类中的Field,Method和Constructor继承自AccessibleObject,因此,通过在这些类上调用setAccessible()方法,我们可以实现对这些字段的操作。

Field gradeField = clazz.getDeclaredField("code");
// 如果是 private 或者 package 权限的,一定要赋予其访问权限
gradeField.setAccessible(true);

8.2、获取Filed两个方法的区别

两者的区别就是 getDeclaredField() 可以获取的 Class 中被 private 修饰的属性。 getField() 方法获取的是非私有属性,并且 getField() 在当前 Class 获取不到时会向祖先类获取。

//获取所有的属性,但不包括从父类继承下来的属性
public Field[] getDeclaredFields() throws SecurityException {}

//获取自身的所有的 public 属性,包括从父类继承下来的。
public Field[] getFields() throws SecurityException {}

8.3、获取Field的类型

  • 可以看到 getGenericType() 确实把泛型都打印出来了,它比 getType() 返回的内容更详细。
public class Demo01Reflect {
    public static void main(String[] args) throws Exception {
        //获取字节码文件对象
        Class aClass = Class.forName("com.chuxuezhe.relfect.Student");
        Field[] filed2 = aClass.getFields();
        for ( Field f : filed2 ) {
            System.out.println("Field :"+f.getName());
            System.out.println("Field type:"+f.getType());
            System.out.println("Field generic type:"+f.getGenericType());
            System.out.println("-------------------");
        }
    }
}
 ----------------输出--------------------
Field :list
Field type:interface java.util.List
Field generic type:java.util.List<com.chuxuezhe.relfect.Person>

8.4、Method获取方法名,获取方法参数

public class Demo01Reflect {
    public static void main(String[] args) throws Exception {
        //获取字节码文件对象
        Class clazz = Class.forName("com.chuxuezhe.relfect.Person");
        //Method获取方法名
        Method[] declaredMethods1 = clazz.getDeclaredMethods();
        for (Method m : declaredMethods1) {
            System.out.println("method name:" + m.getName());
        }
        //获取方法参数
        for (Method m : declaredMethods1) {
            System.out.println("获取方法参数method name:" + m.getName());
            //返回的是一个 Parameter 数组,在反射中 Parameter 对象就是用来映射方法中的参数。
            Parameter[] paras = m.getParameters();
            for (Parameter c : paras) {
                System.out.println("获取参数parameter :" + c.getName() + " " + c.getType().getName());
            }
            //获取所有的参数类型
            Class[] pTypes = m.getParameterTypes();
            for (Class c : pTypes) {
                System.out.print("参数类型method para types:" + c.getName());
            }
            System.out.println();
            System.out.println("==========================================");
        }
    }
}

----------------------输出--------------------------
method name:printAgeByName
method name:getAgeByName
method name:say
获取方法参数method name:printAgeByName
获取参数parameter :arg0 java.lang.String
参数类型method para types:java.lang.String
==========================================
获取方法参数method name:getAgeByName
获取参数parameter :arg0 java.lang.String
参数类型method para types:java.lang.String
==========================================
获取方法参数method name:say

==========================================

8.5、Method方法的invoke()方法执行

Method 调用 invoke() 的时候,存在许多细节:

  • invoke() 方法中第一个参数 Object 实质上是 Method 所依附的 Class 对应的类的实例,如果这个方法是一个静态方法,那么 obj 为 null,后面的可变参数 Object 对应的就是参数。

  • invoke() 返回的对象是 Object,所以实际上执行的时候要进行强制转换。

  • 在对Method调用invoke()的时候,如果方法本身会抛出异常,那么这个异常就会经过包装,由Method统一抛InvocationTargetException。而通过InvocationTargetException.getCause() 可以获取真正的异常。