Java 反射

1. introduce

之前学习Java的过程中详细学习过有关Java反射的内容,但是由于长期没有复习已经有些遗忘了,这里就特意整理下相关内容,希望本篇文章不论是对自己知识的夯实还是对各位工程师都有所帮助。本文主要参考了《Java核心技术(第11版)》,尚硅谷教学视频。希望各位能有时间查看,开卷有益。

本文代码使用 Java 11

2. Java反射的概念

由于Java可以支持用户界面生成器、对象关系映射器以及很多其他需要动态查询类能力的开发工具,因此需要就诞生了反射。

反射:能够分析类能力的程序称为反射

反射机制可以用来:

  • 在运行时分析类的能力。

  • 在运行时检查对象,例如,编写一个适用于所有类的 toString 方法。

  • 实现泛型数组操作代码。

  • 利用 Method 对象,这个对象很像 C++ 中的函数指针。

一般的应用程序员并不需要考虑反射机制(但是各位上进的工程师一定想认真学习(bushi)),一般来说,反射被广泛用于开发工具中(例如开发框架)。

3. Class 类

要深入探索反射机制,首先需要了解的就是啥是Class

3.1 什么是Class类

Class类:在程序运行期间,Java运行时系统始终为所有对象维护一个运行时类型标识。这个信息会跟踪每个对象所属的类。不过,可以使用一个特殊的Java类访问这些信息,而保存这些信息的类就叫做Class。

说人话:Class类就是在Java程序运行期间可以供Java代码调用其他类的工具。

3.2 如何获得Class类

import org.junit.Test;
import java.util.Random;

public class TestReflection {

    // 方法1: 如果类在一个包里,包的名字也作为类名的一部分
    @Test
    public void getClassName1() {
        var generator = new Random();
        Class clazz = generator.getClass();
        String name = clazz.getName();
        System.out.println(name); // java.util.Random
    }

    // 方法2: 还可以使用静态方法 forName 获得类名对应的 Class 对象
    @Test
    public void getClassName2() {
        try {
            String className = "java.util.Random";
            Class clazz = Class.forName(className);
            String name = clazz.getName();
            System.out.println(name); // java.util.Random
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 方法3: T.class 可以代表匹配的类对象
    @Test
    public void getClassName3() {
        Class clazz = Random.class;
        String name = clazz.getName();
        System.out.println(name); // java.util.Random
    }
}

由于JVM会为每个类型管理一个唯一的Class对象。因此,可以使用 == 实现两个类对象的比较。

    // 此时,若 e 是一个 Employee 实例,则为 true;若 e 是 Employee 类的子类或其他类,则为 false。
		@Test
    public void testEmployee() {
        Employee e = new Employee();
        // true
        if (e.getClass() == Employee.class) {
            // ...
        }

        Manager m = new Manager();
        // false
        if (m.getClass() == Employee.class) {
            // ...
        }
    }

3.3 获得Class类能干什么

获取到类对象的 Class 信息后,最常见的方法就是对这个类对象创建一个实例了。

这里就要使用到一个方法 newInstance(),该方法会抛出 InvocationTargetException。

    @Test
    public void testNewInstance() throws Exception {
        Class clazz = Employee.class;
        // 获取Employee类的无参构造器,使用构造器实例化对象
        Object o = clazz.getConstructor().newInstance();
        ((Employee) o).setName("Andrea");
        ((Employee) o).setSalary(1000.00);
        System.out.println(o); // Employee{name='Andrea', salary=1000.0, hireDate=null}
    }

这里需要注意的是,获取到 Class 之后,除了可以实例化之外还可以查看很多信息,这里就留给各位工程师探索吧!

4. Java反射的应用

4.1 利用反射分析类的能力

根据《Java核心技术》所说,这是反射机制最重要的内容 – 检查类的结构。

下面将使用以下例子来说明,如何使用反射分析类

import java.util.*;
import java.lang.reflect.*;

public class ReflectionTest {
    public static void main(String[] args) throws ReflectiveOperationException {
        String name = "java.lang.Double"; // 以Double为例

        Class clazz = Class.forName(name); // 获得 Class 信息
        Class superClazz = clazz.getSuperclass(); // 获取父类信息
        // 返回一个字符串,包含对应modifiers中位设置的修饰符 e.g. public final
        String modifiers = Modifier.toString(clazz.getModifiers());
        if (!modifiers.isEmpty()) {
            System.out.print(modifiers + " ");
        }
        System.out.print("class " + name);
        if (superClazz != null && superClazz != Object.class) {
            System.out.print(" extends " + superClazz.getName());
        }

        System.out.print("\n{\n");
        printConstructors(clazz);
        System.out.println();
        printMethods(clazz);
        System.out.println();
        printFields(clazz);
        System.out.println("}");
    }

    /**
     * 打印所有构造器方法
     */
    public static void printConstructors(Class clazz) {
        // 这里获取所有的构造方法,与getConstructors方法不同的是getConstructors只能返回public的方法,而他可以返回全部构造器
        Constructor[] constructors = clazz.getDeclaredConstructors();

        for (Constructor constructor : constructors) {
            String name = constructor.getName();
            System.out.print("   ");
            String modifiers = Modifier.toString(constructor.getModifiers());
            if (!modifiers.isEmpty()) {
                System.out.print(modifiers + " ");
            }
            System.out.print(name + "(");

            // 打印构造方法中全部参数
            Class[] paramTypes = constructor.getParameterTypes();
            for (int j = 0; j < paramTypes.length; j++) {
                if (j > 0) {
                    System.out.print(", ");
                }
                System.out.print(paramTypes[j].getName());
            }
            System.out.println(");");
        }
    }

    /**
     * 打印class类的所有方法
     */
    public static void printMethods(Class clazz) {
        Method[] methods = clazz.getDeclaredMethods(); // 获取所有方法

        for (Method method : methods) {
            Class retType = method.getReturnType(); // 获取方法的返回值
            String name = method.getName(); // 返回一个表示方法名的字符串

            System.out.print("   ");
            // 返回该方法的修饰符
            String modifiers = Modifier.toString(method.getModifiers());
            if (!modifiers.isEmpty()) {
                System.out.print(modifiers + " ");
            }
            System.out.print(retType.getName() + " " + name + "(");

            // 打印方法的参数
            Class[] paramTypes = method.getParameterTypes();
            for (int j = 0; j < paramTypes.length; j++) {
                if (j > 0) {
                    System.out.print(", ");
                }
                System.out.print(paramTypes[j].getName());
            }
            System.out.println(");");
        }
    }

    /**
     * 打印全部字段
     */
    public static void printFields(Class clazz) {
        // 获取Class的所有字段
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            Class type = field.getType(); // 字段的返回值
            String name = field.getName(); // 字段的名字
            System.out.print("   ");
            String modifiers = Modifier.toString(field.getModifiers()); // 字段的修饰符
            if (!modifiers.isEmpty()) {
                System.out.print(modifiers + " ");
            }
            System.out.println(type.getName() + " " + name + ";");
        }
    }
}

4.2 使用反射在运行时分析对象

上一部分中我们已经知道了,利用反射可以查看任意对象数据字段的名字和类型,这一部分我们就进一步查看字段的具体内容

由于反射一般使用在程序的编译阶段,因此可以利用反射机制查看在编译时还不知道的对象字段,这也就是本部分为什么叫做“在运行时分析对象”的原因。关键方法是 Field.get() 方法。

    @Test
    public void testFieldGet() throws Exception {
        Employee emp = new Employee();
        emp.setName("andrea");
        emp.setSalary(1000.00);
        
        Class clazz = emp.getClass();
        Field f = clazz.getDeclaredField("name");
        // 使用Field.get()方法获取obj,obj的值就是当前字段值 (andrea)
        Object obj = f.get(emp); // java.lang.IllegalAccessException
        System.out.println(obj); // andrea
    }

大家执行上面操作的时候会发现它会报错,主要原因是因为name字段是private类型的,解决方法也很简单,只需要在 f.get(emp) 之前加上一句 f.setAccessible(true) 就好了。除此之外,我们还可以使用 Field.set(obj, val) 方法对字段进行赋值操作。

    @Test
    public void testFieldGet() throws Exception {
        Employee emp = new Employee();
        emp.setName("andrea");
        emp.setSalary(1000.00);
        
        Class clazz = emp.getClass();
        Field f = clazz.getDeclaredField("name");
        // 使用Field.get()方法获取obj,obj的值就是当前字段值 (andrea)
        f.setAccessible(true); // 解决 java.lang.IllegalAccessException
        Object obj = f.get(emp);
        System.out.println(obj); // andrea
        // 使用Field.set(obj,val)方法进行赋值
        f.set(emp,"john");
        Object obj1 = f.get(emp);
        System.out.println(obj1); // john
    }

这个 Field.setAccessible(true) 方法是专门为了调试、持久存储和类似机制提供的。

下面是《Java核心技术》中给的示范代码(感兴趣的工程师可以自行研究,受益匪浅)

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;

public class ObjectAnalyzer {

    private ArrayList<Object> visited = new ArrayList<>();

    /**
     * Converts an object to a string representation that lists all fields.
     */
    public String toString(Object obj) throws ReflectiveOperationException {
        if (obj == null) {
            return "null";
        }
        if (visited.contains(obj)) {
            return "...";
        }

        visited.add(obj);

        Class clazz = obj.getClass();
        if (clazz == String.class) {
            return (String) obj;
        }
        if (clazz.isArray()) {
            String str = clazz.getComponentType() + "[]{"; // class java.lang.Object[]{
            for (int i = 0; i < Array.getLength(obj); i++) {
                if (i > 0) {
                    str += ",";
                }
                Object val = Array.get(obj, i);
                if (clazz.getComponentType().isPrimitive()) {
                    str += val;
                } else {
                    str += toString(val); // 递归调用 toString 方法
                }
            }
            return str + "}";
        }

        String str = clazz.getName();
        // inspect the fields of this class and all superclasses
        do {
            str += "[";
            Field[] fields = clazz.getDeclaredFields();
            AccessibleObject.setAccessible(fields, true);
            // get the names and values of all fields
            for (Field f : fields) {
                if (!Modifier.isStatic(f.getModifiers())) {
                    if (!str.endsWith("[")) {
                        str += ",";
                    }
                    str += f.getName() + "=";
                    Class t = f.getType();
                    Object val = f.get(obj);
                    if (t.isPrimitive()) {
                        str += val;
                    } else {
                        str += toString(val);// 递归调用 toString 方法
                    }
                }
            }
            str += "]";
            clazz = clazz.getSuperclass();
        } while (clazz != null);

        return str;
    }
}
import java.util.*;

/**
 * This program uses reflection to spy on objects.
 */
public class ObjectAnalyzerTest {

    public static void main(String[] args) throws ReflectiveOperationException {
        var squares = new ArrayList<Integer>();
        for (int i = 1; i <= 5; i++) {
            squares.add(i * i);
        }
        System.out.println(new ObjectAnalyzer().toString(squares));
    }
    
}

4.3 实战:使用反射编写泛型数组代码

java.lang.reflect 包中的 Array 类允许动态地创建数组。

下面来段烂代码:

	public static Object[] badCopyOf(Object[] a, int newLength) {
        var newArray = new Object[newLength];
        System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
        return newArray;
    }

这段代码会返回一个Object类型的数组,不能转换为目标类型的数组。由于Java数组会记住每个元素的类型,即创建数组时new表达式中使用的元素类型。说人话就是:Employee[] --> Object[] 是OK的,但是 Object[] --> Employee[] 就是不行的。

为了编写代码的通用性,我们需要使用 Array.newInstance()方法创建与原数组类型相同的新数组。

要获得新数组的元素类型,需要完成以下步骤:

  1. 获得a数组的类对象
  2. 确认它确认它确实是一个数组
  3. 使用 Class.getComponentType() (只为表示数组的类对象定义了这个方法)确定数组的正确类型

下面就是优秀的代码了:

import java.lang.reflect.*;
import java.util.*;

public class CopyOfTest {

    public static void main(String[] args) {
        int[] a = {1, 2, 3};
        a = (int[]) goodCopyOf(a, 10);
        System.out.println(Arrays.toString(a));

        String[] b = {"Tom", "Dick", "Harry"};
        b = (String[]) goodCopyOf(b, 10);
        System.out.println(Arrays.toString(b));
    }
  
    public static Object goodCopyOf(Object a, int newLength) {
        // 1. 获得a数组的类对象
        Class clazz = a.getClass();
        // 2. 确认它确实是个数组
        if (!clazz.isArray()) {
            return null;
        }
        // 3. 使用 Class.getComponentType() 方法确定数组的正确类型
        Class componentType = clazz.getComponentType();
        int length = Array.getLength(a);
        // 构造一个新数组
        Object newArray = Array.newInstance(componentType, newLength);
        // 复制 a数组 到 新数组 中
        System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
        return newArray;
    }
}

4.4 调用任意方法和构造器

4.4.1 如何调用方法

使用 Method.invoke() 方法调用包装在当前 Method 对象中的方法。

方法签名:Object invoke(Object obj, Object... args)

第一个参数是隐式参数,其余的对象提供了显式参数。对于静态方法,第一个参数可以忽略,即可以将其设置为null。

!需要注意的是,这里如果返回类型是基本类型,invoke方法会返回其包装器类型。可以使用自动拆箱将其进行转换。

4.4.2 如何得到目标方法

方法1: 可以使用 4.1 中使用的 getDeclaredMethods() 方法,然后搜索返回的 Methods 对象,得到想要的方法。显然这种方法很麻烦,建议使用下面介绍的方法2 。

方法2: 调用 Class.getMethod() 得到想要的方法,不过需要考虑方法重名的问题,因此这个方法的参数列表需要填写目标方法的参数。

方法签名:Method getMethod(String name, Class... parameterTypes)

例如:Method method = Employee.class.getMethod(“raiseSalary”, double.class);

4.4.3 如何得到构造器

跟得到目标方法类似,可以使用 Class.getConstructor() 方法得到构造器,然后使用 newInstance() 方法进行实例化(还记得这个方法吗?)

例如:Constructor cons = class.getConstructor(long.class);

4.4.4 代码演示
import java.lang.reflect.*;

public class MethodTableTest {

    public static void main(String[] args) throws ReflectiveOperationException {
        // 获取目标方法
        Method square = MethodTableTest.class.getMethod("square", double.class);
        Method sqrt = Math.class.getMethod("sqrt", double.class);

        printTable(1, 10, 10, square);
        printTable(1, 10, 10, sqrt);
    }

    /**
     * 计算平方
     */
    public static double square(double x) {
        return x * x;
    }

    /**
     * 打印 x-y 的计算结果
     *
     * @param from   x
     * @param to     y
     * @param n      the number of rows in the table
     * @param method 目标方法
     */
    public static void printTable(double from, double to, int n, Method method) throws ReflectiveOperationException {
        // print out the method as table header
        System.out.println(method);

        double dx = (to - from) / (n - 1);

        for (double x = from; x <= to; x += dx) {
            // 调用目标方法
            double y = (Double) method.invoke(null, x);
            System.out.printf("%10.4f | %10.4f%n", x, y);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值