Java中使用反射设置私有字段的最短、最佳、最清晰的方法是什么?

10

你好,我之前已经在Java中使用过反射技术。但是如果你正在使用Java标准方式(例如注入私有字段),你需要编写大量代码来完成工作。

在Java对象中注入私有字段的最短方式是什么?是否有广泛使用且已经生产就绪的库实现了此功能?


1
请注意,这在模块化的Java应用程序中可能无法正常工作。如果包含私有字段的包未被其模块打开,则无论如何都无法更改它们。 - VGR
@VGR,你能发一下关于那件事的链接吗? - Andreas Hauschild
https://www.oracle.com/corporate/features/understanding-java-9-modules.html - VGR
4个回答

18

不使用外部库,您需要:

  • 获取Field实例
  • 将该字段实例设置为可访问
  • 设置新值

如下所示:

Field f1 = obj.getClass().getDeclaredField("field");
f1.setAccessible(true);
f1.set(obj, "new Value");

我喜欢这种方法,因为它不依赖于任何外部库。但是,无论如何它都取决于每个用户场景。 - Ankur
2
在设置完值之后,您应该将可访问性重新设置为“false”。 - Andreas Hauschild
1
@AndreasHauschild 如果您不重新使用字段f1,则无需将accessible再次设置为false。您实际上并没有修改原始类中字段的可见性,而只是明确表示您想访问字段f1的私有内容。 - Davide Lorenzo MARINO
真的,但如果你改变了给定对象,它可能会成为问题。如果它被其他你无法控制的代码使用,这可能会造成麻烦。 - Andreas Hauschild
2
@AndreasHauschild 不,如果你使用 Field fNew = obj.getClass().getDeclaredField("field"); 创建一个新的 Field 变量,你不能在没有显式设置 accessible 为 true 的情况下更改它。这意味着更改仅对 Field f1 实例局部有效。 - Davide Lorenzo MARINO
@DavideLorenzoMARINO 谢谢你的提示。你是对的。又学到了一些东西 ;) - Andreas Hauschild

14

这里所说的“一行简述”

FieldUtils.writeField(Object target, String fieldName, Object value, boolean forceAccess)
如果您的项目使用 Apache Commons Lang,则通过反射设置值的最简单方式是使用类 'org.apache.commons.lang3.reflect.FieldUtils' 中的静态方法 'writeField'
下面的简单示例显示了一个带有字段 paymentService 的书店对象。该代码展示了如何使用不同的值两次设置私有字段。
import org.apache.commons.lang3.reflect.FieldUtils;

public class Main2 {
    public static void main(String[] args) throws IllegalAccessException {
        Bookstore bookstore = new Bookstore();

        //Just one line to inject the field via reflection
        FieldUtils.writeField(bookstore, "paymentService",new Paypal(), true);
        bookstore.pay(); // Prints: Paying with: Paypal

        //Just one line to inject the field via reflection
        FieldUtils.writeField(bookstore, "paymentService",new Visa(), true);
        bookstore.pay();// Prints Paying with: Visa
    }

    public static class Paypal implements  PaymentService{}

    public static class Visa implements  PaymentService{}

    public static class Bookstore {
        private  PaymentService paymentService;
        public void pay(){
            System.out.println("Paying with: "+ this.paymentService.getClass().getSimpleName());
        }
    }
}

您可以通过Maven Central获取该库:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>

1
如果库中有隐藏的代码,那么它就不是一行代码了。这就像是创建一个大函数并从一个方法中调用它,然后称其为一行代码。 - SamHoque
4
@SamzSakerz,我理解你的意思,但这个论点可以适用于被认为是“一句话”的99%的内容。 - Slaw

3
如果您正在使用Spring框架,有一个名为ReflectionUtils的实用程序类。以下是私有字段注入值的方法。
根据findField(Class clazz, String name, Class type)和setField(Field field, @Nullable Object target, @Nullable Object value),您可以编写如下代码:
final Field field = ReflectionUtils.findField(Foo.class, "name", String.class)
ReflectionUtils.setField(field, targetObject, "theNewValue")

这样写并不是最短的方式,但比使用基本的JDK工具更短,而且我相信开发Spring的人们,所以我很有信心。在执行此操作之前,请确保您确实需要使用反射。

如果这是为了测试目的,还有ReflectionTestUtils也提供了一些方法。

您可以在文档这里中找到所有内容。

希望能对您有所帮助。


与ReflectionTestUtils相反,Spring的ReflectionUtils仅供内部使用。您可以使用它,但在将来的版本中可能会没有警告而发生变动。 - Pieter De Bie

0
我会将这个方法改为辅助方法。
public static void changeField(Class cc, String field, String newValue) throws NoSuchFieldException, IllegalAccessException {
    Field f = cc.getDeclaredField(field);
    f.setAccessible(true);
    f.set(cc, newValue);
    f.setAccessible(false);
}

并像以下这样调用它

changeField(cc, "field", "newValue");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接