Java 反射

87 阅读4分钟

除了int等基本类型外,Java的其他类型全部都是class(包括interface

JVM为每个加载的classinterface创建了对应的Class实例来保存classinterface的所有信息;

获取一个class对应的Class实例后,就可以获取该class的所有信息;

通过Class实例获取class信息的方法称为反射(Reflection);

JVM总是动态加载class,可以在运行期根据条件来控制加载class。

1、访问字段

1、对于任意Object实例,我们只要获取到了它的class,我们就能获取它所有的信息。
-   Field getField(name):根据字段名获取某个public的field(包括父类)
-   Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
-   Field[] getFields():获取所有public的field(包括父类)
-   Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

2、拿到class的field之后,可以知道这个field叫什么名字。
-   getName():返回字段名称,例如,"name";
-   getType():返回字段类型,也是一个Class实例,例如,String.class;
-   getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
    
    当用 getModifiers 获取到字段的修饰符之后,可以在进行类型判断。
    
3、获取 field 的值
    当获取到 field 之后,只是第一步,然后我们可以再获取这个field的值
    Class cls = d.getClass();  // 这个d是实例化对象
    Field[] fields = cls.getDeclaredFields(); // 获取实例对象d的所有字段信息。
    Object value = field.get(d) // 我们再拿到具体的field之后,再利用get获取到值。
    
    注意:调用Field.setAccessible(true)的意思是,别管这个字段是不是public,一律允许访问。这个要写在前面。
    
4、修改 field 的值
    以上面为例,field.set(d,"xxx") 第一个参数是实例的对象,第二个参数是修改后的内容。
   

2、调用方法

1、获取所有方法
-   Method getMethod(name, Class...):获取某个public的Method(包括父类)
-   Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
-   Method[] getMethods():获取所有public的Method(包括父类)
-   Method[] getDeclaredMethods()`:获取当前类的所有Method(不包括父类)

一个 Method 对象包含一个方法的所有信息。
-   getName():返回方法名称,例如:"getScore";
-   getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
-   getParameterTypes():返回方法的参数类型,是一个Class数组,例如:`{String.class, int.class}`;
-   getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。

2、调用方法

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class diaoyongfangfa {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        String name = "hello world!";
        String r = name.substring(2,5); // llo
        System.out.println(r);
        // 利用反射来调用 substring 方法
        Class cls = name.getClass();
        Method m = cls.getMethod("substring", int.class,int.class);
        String r2 = (String)m.invoke(name,2,5); // llo
        System.out.println(r2);
        /*
        * 1、invoke(调用),上面是调用 String 的substring方法,该方法有多个版本,可以在 cls.getMethod("substring", int.class,int.class);
        * 定义 int 的数量来调用不同的版本。
        * 2、对 Method 实例调用 invoke 就相当于调用该方法,invoke 的一个参数是对象实例,后面的参数与 cls.getMethod() 除了第一个参数之外的参数相同即可。
        * */
    }
}

3、调用非public方法

和 filed 类似,通过 Method.setAccessible(true) 允许其调用
可能会失败。

4、多态

使用反射调用方法时,仍然遵循多态原则
即总是调用实际类型的覆写方法。

5、调用构造方法

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

public class diaoyonggouzaofangfa {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Demo d = new Demo();
        // 通过反射来创建新实例
        Demo d2 = Demo.class.newInstance();
        d2.setName("chen");
        System.out.println(d2.name);
        /*
        * 1、上面的只能调用 public 的无参构造方法,有参或者不是public,就无法直接通过newInstance 来调用。
        * 2、为了调用任意的构造方法,提供了 Constructor 对象,
        * 3、下面的时利用反射,调用了有参构造方法,再利用反射,获取getName方法,然后在调用invoke传入利用反射构造的有参实例,执行。
        * */
        // 通过反射来创建新实例并给name字段命名
        Class<Demo> d3 = Demo.class;
        // 获取带有参数的构造方法
        Constructor<?> constructor = d3.getConstructor(String.class);
        // 调用构造方法,创建对象
        Object d3Object = constructor.newInstance("King");
        System.out.println(d3Object);
        Method d3Method = d3.getMethod("getName");
        System.out.println(d3Method.invoke(d3Object));
    }
}

通过Class实例获取Constructor的方法如下:

-   getConstructor(Class...):获取某个public的Constructor;
-   getDeclaredConstructor(Class...):获取某个Constructor;
-   getConstructors():获取所有public的Constructor;
-   getDeclaredConstructors():获取所有Constructor。

注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。

调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。

6、获取继承关系

7、动态代理