【Core Java】04 Java 反射

7 篇文章 0 订阅
本文深入探讨Java反射机制,包括如何获取Class实例,以及Field、Method和Constructor的使用。通过反射,可以访问私有域、调用私有方法,并在运行时构造对象。同时,介绍了setAccessible方法来绕过访问检查,以及Method的invoke方法用于方法调用。慎用反射,避免安全性问题。
摘要由CSDN通过智能技术生成

反射

Class 类

Java 程序运行期间,JVM 会为每一个类维护一个(仅一个)Class的实例,该实例用来描述类的信息。

获取实例

每个类都会从顶级类中继承这个方法,用于返回该类唯一的Class实例。

Class getClass();

或者使用Class的静态方法,通过字符串得到Class实例:

static Class forName(String className);

最后一种方法是通过class关键字,例如:

Integer.class;
int.class;
// 基本类型也有 Class 实例

此外,Class有一个方法,用于调用该类的无参构造从而得到一个新实例:

Object newlnstance();

Field、Method 和 Constructor 类

获得他们的实例

这些类用来描述域、方法和构造器。

通过Class里的方法,返回对应数组:

Field[] getFields();
// 返回该类及超类的公有域 
Filed[] getDeclaredFie1ds();
// 返回该类的全部域 

Method[] getMethods();
// 返回该类及超类的公有方法
Method[] getDeclareMethods();
// 返回该类及接口的全部方法

Constructor[] getConstructors();
// 返回全部公有构造器
Constructor[] getDeclaredConstructors();
// 返回所有构造器

或者通过字符串描述来获得特定的实例:

Field[] getField(String name);
Filed[] getDeclaredFie1d(String name);
// 通过域名获取 Field 实例

Method[] getMethod(String name, Class<?>... parameterTypes);
Method[] getDeclareMethod(String name, Class<?>... parameterTypes);
// 通过方法名及参数列表的 Class 实例获取 Method 实例

Constructor[] getConstructor(Class<?>... parameterTypes);
Constructor[] getDeclaredConstructor(Class<?>... parameterTypes);
// 通过参数列表的 Class 实例获取 Constructor 实例
名称

Field、Method 和 Constructor 类都有一个getName方法来获得名称。

String getName();
修饰符

还有一个getModifiers方法来得到修饰符。

int getModifiers();

返回的 int 包含所有修饰符的是否使用的状态,需要通过Modifier类中的静态方法来解析:

static String toString(int modifiers);
static boolean isAbstract(int modifiers);
static boolean isFinal (int modifiers);
static boolean islnterface(int modifiers);
static boolean isNative(int modifiers);
static boolean isPrivate(int modifiers);
static boolean isProtected(int modifiers);
static boolean isPubl(int modifiers);
static boolean isStat(int modifiers);
static boolean isStrict(int modifiers);
static boolean isSynchronized(int modifiers);
static boolean isVolatile(int modifiers);
fields 的类型

特别地,Field 拥有一个getType方法,来获得域的Class实例。

Class getType();
参数列表及异常

Constructor 和 Method 类拥有getParameterTypesgetExceptionTypes方法,来获得参数列表的类型及抛出的异常类型。

Class[] getParameterTypes();
Class[] getExceptionTypes();
返回值类型

Method 拥有一个getReturnType类,获得返回值的类型。

Class getReturnType();

运行时操作

我们知道,Java 特性有可能是编译器特性,也可能是解释器特性。

例如枚举类enumvalues方法将返回所有枚举实例的数组,不过很少有人去思考这个数组究竟存在于何处,以为是什么可怕的魔法。然而这实际上是编译器的行为。

我们通过反射来验证。

对于枚举类:

enum State {
    Success, Fail;
}

我们拿到该类的域数组,然后打印出它们的名称:

Arrays.asList(State.class.getDeclaredFields())
    .forEach(item -> System.out.println(item.getName()));

输出:

Success

Fail

$VALUES

出现了一个奇怪的符号$VALUES,实际上它就是values方法返回的数组。

下面介绍几个可怕的方法,来操作我们本来无法操作的域:

运行时操作域

Field 的方法:

Object get(Object obj);
// 传入一个实例,返回该实例的域值

void set(Object obj ,Object newValue);
// 修改某个实例下该域的值

在查看$VALUES之前,我们还有一个安全机制的问题需要讨论。

如果$VALUES是一个私有域,那我们岂不是绕过了类的访问检查?到目前为止并不会,如果你看到这里就已经试图去获取该域值,那么会抛出一个IllegalAccessException异常。

可以使用 Field 的setAccessible方法来设置可访问标识:

void setAccessible(boolean flag);
// 为反射对象设置可访问标志,flag 为 true 表明屏蔽 Java 语言的访问检查,使得对象的私有属性也可以被 get 和 set
boolean isAccessible();
// 返回反射对象的可访问标志的值
static void setAccessible(AccessibleObject[] array, boolean flag); 
// 批量为反射对象设置可访问标志

反射对象指 Field、Method 和 Constructor 等类。

现在,我们真的已经绕过了类的访问检查。

$VALUES确实是一个私有域,可以通过前面提到过的Modifier类来验证:

System.out.println(Modifier.isPrivate(State.class.getDeclaredField("$VALUES").getModifiers()));

输出:

true

测试:

注意,这两个方法必须进行异常处理。

try {
    Field values = State.class.getDeclaredField("$VALUES");
    values.setAccessible(true);
    Arrays.asList((State[]) values.get(State.Success))
        .forEach(System.out::println);
} catch (Exception e) {
    e.printStackTrace();
}

输出:

Success

Fail

运行时构造对象

由于 Java 禁止不安全的类型转换(向下转型),所以实现通用的方法就很困难。

通用的方法,意味着对任意类型均可用。例如一个接受 Object 参数的方法,此时若需要构造一个该类型的新实例该怎么办呢?

使用Constructor的静态方法newInstance

它不同于ClassnewInstanceConstructor的允许调用非空参构造器。

Object newlnstance(Object[] args);

现在看一个例子,实现Arrays.copyOf方法:

想要实例化一个数组,可以使用 Array 的一个静态方法:

static Object newInstance(Class<?> componentType, int length);
// 通过数据类型和长度构造一个数组

第一个参数通过ClassgetComponentType方法获得:

Class getComponentType();

实现如下:

通过多态取到元素类型的Class实例,然后调用Array.newInstance实例化一个数组。

public static Object[] expend(Object[] arr, int length) {
    Object[] newArr = (Object[]) Array.newInstance(arr.getClass().getComponentType(), length);
    for (int i = 0; i < Math.min(arr.length, newArr.length); i++) {
        newArr[i] = arr[i];
    }
    return newArr;
}

方法指针

方法指针指的是 Method 类的实例。关于获得 Method 实例的几种方法,不管是获取特定的还是获取全部的,都在本节开头详细罗列了。

每个 Method 实例都有一个 invoke方法,用来调用该 Method 对象描述的方法:

Object invoke(Object obj, Object... args)

第一个参数是方法的隐式参数,即this指向的对象,或理解为调用该方法的对象。接下来的是该方法的参数列表。

对于静态类型,第一个参数可以传入null,或者传入任意值(毕竟在静态方法中不需要使用this)。

这个invoke方法类似 Fields 中的getset方法,对于访问受限的方法,我们也需要对相应 Method 实例调用setAccessible方法,绕过访问检查。

如非必要,不要使用函数指针来调用方法,很麻烦,且容易出错。

如果需要回调函数,应该使用接口或 lambda。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高厉害

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值