跳至主要內容

反射

Mars大约 12 分钟java反射reflectionjava

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

反射主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义

反射的能力

  • 在运行时分析类的能力
  • 在运行时分析对象的能力 (判断任意一个对象所属的类、 访问对象的属性,方法,构造方法等)
  • 使用反射可以编写泛型数组代码

反射技术的依赖

java.lang.Class 类和 java.lang.reflect 包下的类库一起为反射提供了支持。

java.lang.Class

Class 简介

Java 中除了有基本数据类型外,还有一种叫做 class 的类型。 JVM 每读取到一种 class 类型时,就会创建一个类型是 class 名字叫 Class 的实例对象。 JVM 每编译一个新类就会创建一个 Class 的实例对象。任意的 Java 类型(包括 void 关键字)都有 Class 对象。 Class 对象对应着 java.lang.Class 类,如果说类是对象的抽象和集合,我们也可以这样来理解 Class 类就是对类的抽象和集合,从下面这段 JDK 的源码中也可以看出 Class 类实际上是一个泛型类,而且Class 类的构造方法是 private,说明 Class 类的实例对象不能通过 new 关键字来创建,而是在类加载的时候由 JVM 以及类加载器来自动创建的。

package java.lang;

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement,
                              TypeDescriptor.OfField<Class<?>>,
                              Constable {
    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader, Class<?> arrayComponentType) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
        componentType = arrayComponentType;
    }
}

了解过上述原理后,我们现在可以更加清楚地了解反射是什么了。由于JVM 会为每个加载的 class 类型都创建对应的 Class 实例对象,该实例对象中保存有该 class 的所有信息,包括类名、包名、父类、 实现的接口、包含的成员变量及成员方法等,因此如果获取到了某个 Class 实例对象,就可以通过该对象获取到实例对应的 class 的所有信息。 这种通过 Class 实例获取 class 信息的方法称为反射。

获取 Class 对象的方式

我们有三种方式可以获取 Class 对象

  1. 通过 实例对象.getClass() 访问一个实例变量的 getClass() 方法

    String s = "Hello, world!";
    Class clazz = s.getClass();
    
  2. 通过 类名.class 访问一个 class 的静态变量来获取

    Class clazz = String.class;
    
  3. 通过 Class.forName("类的全限定名")

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

Class 对象的唯一性

每个通过关键字 class 标识的类,在内存中有且只有一个与之对应的 Class 对象来描述其类型信息,不论通过哪种方式获取该类的 Class 对象, 它们返回的都是指向同一个 java 堆地址上的 Class 引用,JVM 不会创建两个相同类型的 Class。 也就是我们常说的每个类,无论创建多少个实例, 在 JVM 中都对应同一个 Class 对象。Java 虚拟机中使用双亲委派模型来协调不同类加载器之间如何工作,以此保证了 Class 对象的唯一性。 下面这段代码便可以验证 Class 对象的唯一性。

package com.csthink.reflect;

public class Robot {

    private String name;

    static {
        System.out.println("Hello everyone!");
    }

    public void say(String guestName) {
        System.out.println("Hello, " + guestName + ", my name is " + name);
    }

    private String work(String tag, Integer num) {
        return "tag is " + tag + ", num is " + num;
    }

    public static void main(String[] args) throws ClassNotFoundException {
        // 第一种方式获取 Class 对象
        Robot robot = new Robot();
        Class<? extends Robot> robotClass1 = robot.getClass();
        System.out.println("获取 Class 对象方式 1: " + robotClass1.getName());

        // 第二种方式获取 Class 对象
        Class<Robot> robotClass2 = Robot.class;
        System.out.println("获取 Class 对象方式 2: " + robotClass2.getName());

        // 因为 Class 实例在JVM中是唯一的,所以,上述方法获取的 Class 实例是同一个实例。可以用 == 比较两个 Class 实例:
        // 判断第一种方式获取的 Class 对象和第二种方式获取的是否是同一个
        System.out.println(robotClass1 == robotClass2);

        // 第三种方式获取 Class 对象(常用)
        Class<?> robotClass3 = Class.forName("com.csthink.reflect.Robot");
        System.out.println("获取 Class 对象方式 3: " + robotClass3.getName());

        // 判断第二种方式获取的 Class 对象和第三种方式获取的是否是同一个
        System.out.println(robotClass2 == robotClass3);
    }
}

三种不同方式获取的 Class 对象,== 检查的结果均为 true ,表示它们都是同一个堆上的 Class

java.lang.reflect 包类库

  • Field: 表示类中的成员变量
  • Method: 表示类中的成员方法
  • Constructor: 表示类的构造方法
  • Array: 该类提供了动态创建数组和访问数组元素的静态方法

使用反射的简单示例

以下是一个通过反射的方式来操作 Robot 实体类中的成员变量和成员方法的示例。

Robot
package com.csthink.reflect;


public class Robot {

    private String name;

    static {
        System.out.println("Hello everyone!");
    }

    public void say(String guestName) {
        System.out.println("Hello, " + guestName + ", my name is " + name);
    }

    private String work(String tag, Integer num) {
        return "tag is " + tag + ", num is " + num;
    }
}

反射相关的常用方法

获取类的构造方法并使用

提示

# 批量获取构造方法

# 获取所有"公有的"构造方法
public Constructor[] getConstructors() 

# 获取所有的构造方法(包括私有的、受保护的、默认、公有)
public Constructor[] getDeclaredConstructors() 

# 获取单个构造方法

# 获取单个的"公有的"构造方法
public Constructor getConstructor(Class<?>... parameterTypes) 

# 获取"某个构造方法"可以是私有的,或受保护的、默认、公有
public Constructor getDeclaredConstructor(Class<?>... parameterTypes)

# 调用构造方法
Constructor --> newInstance(Object... args)

获取类的成员变量并使用

提示

# 批量获取成员变量

# 获取所有"公有的"成员变量
public Field[] getFields()

# 获取所有的构造方法(包括私有的、受保护的、默认、公有)
public Field[] getDeclaredFields() 

# 获取单个构造方法

# 获取单个的"公有的"成员变量,包括继承的字段
public Field getField(String fieldName) 

# 获取某个成员变量(可以是私有的,或受保护的、默认、公有),不包括继承的字段
public Field getDeclaredField(String fieldName) 

# 设置字段值:
Field --> public void set(Object obj, Object value):
# 参数说明:
- obj: 要设置的字段所在的对象
- value: 要为字段设置的值

获取类的成员方法并使用

提示

# 批量获取成员方法

# 获取所有"公有的"成员方法(包含了父类的方法也包含Object 类)
public Method[] getMethods()

# 获取所有的成员方法(包括私有的、受保护的、默认、公有)不包括继承的
public Method[] getDeclaredMethods()

# 获取单个构造方法

# 获取单个的"公有的"成员变量,包括继承的方法
public Method[] getMethods(String name, Class<?>... parameterTypes) 

# 获取某个成员方法(可以是私有的,或受保护的、默认、公有),不包括继承的方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

# 调用方法:
Method --> public Object invoke(Object obj,Object...args):
# 参数说明:
- obj: 要调用方法的对象
- value: 调用方法时传递的实参

获取注解方法

提示

# 批量获取注解方法

public Annotation[] getAnnotations ()

public Annotation[] getDeclaredAnnotations()

# 获取单个注解方法

public <A extends Annotation> A getAnnotation(Class<A> annotationClass)

public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass)

# 调用构造方法
Constructor --> newInstance(Object... args)

创建对象的方法

提示

T newInstance() 创建此 Class 对象所表示的类的一个新实例

获取名称相关的方法

提示

# 返回底层类的规范化名称
String getCanonicalName()

# 返回全限定名(全限定名:包名.类名)
String getName()

# 返回源代码中给出的底层类的简称
String getSimpleName()

判断类型相关的方法

提示

# 判断是不是局部类,也就是方法里面的类
boolean isLocalClass()

# 判断是不是成员内部类,也就是一个类里面定义的类
boolean isMemberClass()

# 判断当前类是不是匿名类,匿名类一般用于实例化接口
boolean isAnonymousClass()

# 如果指定类型的注释存在于此元素上
isAnnotationPresent (Class<? extends Annotation> annotationClass)

# 判断当前Class对象是否是注释类型
boolean isAnnotation()

# 判断该类是否是枚举类
boolean isEnum()

# 判断指定的 Object 是否 Class 的实例
boolean isInstance(Object obj)

# 判断指定的 Class 对象是否表示一个接口类型
boolean isInterface()

# 判断指定的 Class 对象是否表示一个基本类型
boolean isPrimitive()

获取 Class 的修饰符

提示

返回此类或接口以整数编码的 Java 语言修饰符。 public int getModifiers() 我们可以使用 Class.getModifiers() 获得调用类的修饰符的二进制值,然后使用 Modifier.toString(int modifiers) 将获取到的二进制值转换为字符串。

示例代码

ReflectTarget
package com.csthink.reflect;

public class ReflectTarget extends ReflectTargetOrigin{


    // ----------- 构造函数 --------
    // (默认的带参数构造函数)
    ReflectTarget(String str) {
        System.out.println("(默认)的构造方法 s = " + str);
    }

    // 无参数构造函数
    public ReflectTarget() {
        System.out.println("调用了公有的无参构造方法");
    }

    // 有一个参数构造函数
    public ReflectTarget(char name) {
        System.out.println("调用了带有一个参数的构造方法,参数值为 " + name);
    }
    // 有多个参数的构造函数
    public ReflectTarget(String name, int index) {
        System.out.println("调用了带有多个参数的构造方法,参数值为【目标名】: " + name + "【序号】" + index);
    }

    // 受保护的构造函数
    protected ReflectTarget(boolean n) {
        System.out.println("受保护的构造函数 n:" + n);
    }

    // 私有的构造函数
    private ReflectTarget(int index) {
        System.out.println("私有的构造函数 序号" + index);
    }

    // ----------- 成员变量 --------
    public String name;
    protected int index;
    char type;
    private String targetInfo;

    @Override
    public String toString() {
        return "ReflectTarget{" +
                "name='" + name + '\'' +
                ", index=" + index +
                ", type=" + type +
                ", targetInfo='" + targetInfo + '\'' +
                '}';
    }

    // ----------- 成员方法 --------
    public void show1(String s) {
        System.out.println("调用了公有的, String 参数的 show1(): s = " + s);
    }

    protected void show2() {
        System.out.println("调用了受保护的, 无参数的 show2()");
    }

    void show3() {
        System.out.println("调用了默认的, 无参数的 show3()");
    }

    private String show4(int index) {
        System.out.println("调用了私有的,并且有返回值的,int 参数的show4(): index = " + index);
        return "show4result";
    }
}
  • 获取构造函数的示例
package com.csthink.reflect;

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

/**
 * 通过 Class 对象访问并操作某个类 {@link ReflectTarget} 中的构造函数
 */
public class ConstructorCollector {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class clazz = Class.forName("com.csthink.reflect.ReflectTarget");
        // 1. 获取所有"公有的"构造方法
        System.out.println("************************所有公有的构造方法************************");
        Constructor[] conArray = clazz.getConstructors();
        for (Constructor c : conArray) {
            System.out.println(c);
        }

        // 2. 获取所有构造方法
        System.out.println("************************所有的构造方法(包括私有的、受保护的、默认、公有)************************");
        conArray = clazz.getDeclaredConstructors();
        for (Constructor c : conArray) {
            System.out.println(c);
        }

        // 3. 获取带参数的公有构造方法:
        System.out.println("************************获取公有、有两个参数的构造方法************************");
        Constructor con = clazz.getConstructor(String.class, int.class);
        System.out.println("con = " + con);

        // 4. 获取单个私有的构造方法
        System.out.println("************************获取私有构造方法************************");
        con = clazz.getDeclaredConstructor(int.class);
        System.out.println("private con = " + con);

        System.out.println("************************调用私有构造方法************************");
        // 暴力访问,忽略访问修饰符
        con.setAccessible(true);
        ReflectTarget reflectTarget = (ReflectTarget) con.newInstance(1);
    }
}

输出:

  • 获取成员方法的示例
package com.csthink.reflect;

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

/**
 * 获取成员方法并调用
 */
public class MethodCollector {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 1. 获取 Class 对象
        Class clazz = Class.forName("com.csthink.reflect.ReflectTarget");

        // 2. 获取所有公有方法
        System.out.println("************************获取所有的 public 方法,包括父类和Object ************************");
        Method[] methodArray = clazz.getMethods();
        for (Method m : methodArray) {
            System.out.println(m);
        }

        // 3. 获取该类的所有方法
        System.out.println("************************获取所有的方法,包括私有的 ************************");
        methodArray = clazz.getDeclaredMethods();
        for (Method m : methodArray) {
            System.out.println(m);
        }

        // 4. 获取单个公有的方法
        System.out.println("************************获取公有的show1方法 ************************");
        Method m = clazz.getMethod("show1", String.class);
        System.out.println(m);

        // 5. 调用 show1 并执行
        ReflectTarget reflectTarget = (ReflectTarget) clazz.getConstructor().newInstance();
        m.invoke(reflectTarget, "待反射方法一号");

        // 6. 获取一个私有的成员方法
        System.out.println("************************获取私有的show4方法 ************************");
        m = clazz.getDeclaredMethod("show4", int.class);
        System.out.println(m);
        m.setAccessible(true);
        String result = String.valueOf(m.invoke(reflectTarget, 20));
        System.out.println(result);
    }
}

输出:

  • 获取成员变量的示例
package com.csthink.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
 * 通过 Class 对象访问并操作某个类 {@link ReflectTarget} 中的成员变量
 */
public class FieldCollector {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 获取 Class 对象
        Class clazz = Class.forName("com.csthink.reflect.ReflectTarget");

        // 1. 获取所有的公有字段
        System.out.println("************************所有公有的字段************************");
        Field[] fieldArray = clazz.getFields();
        for (Field f : fieldArray) {
            System.out.println(f);
        }

        // 2. 获取所有的字段
        System.out.println("************************所有的字段************************");
        fieldArray = clazz.getDeclaredFields();
        for (Field f : fieldArray) {
            System.out.println(f);
        }

        // 3. 获取单个特定公有的字段
        System.out.println("************************获取公有字段并调用************************");
        Field f = clazz.getField("name");
        System.out.println("公有的 field name " + f);
        ReflectTarget reflectTarget = (ReflectTarget) clazz.getConstructor().newInstance();

        // 4. 给获取到的field赋值
        f.set(reflectTarget, "待反射一号");

        // 5. 验证对应的值 name
        System.out.println("验证name:" + reflectTarget.name);

        // 6. 获取单个私有的 Field
        System.out.println("************************获取私有字段 targetInfo 并调用************************");
        f = clazz.getDeclaredField("targetInfo");
        System.out.println(f);
        f.setAccessible(true);
        f.set(reflectTarget, "15056111244");
        System.out.println("验证信息" + reflectTarget);

    }
}

输出: