我在第三方JAR中有一个设计很差的类,我需要访问它的一个私有字段。例如, 为什么我需要选择私人领域是必要的?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

如何使用反射来获取stuffIWant的值?


当前回答

如果使用Spring:

在测试上下文中,ReflectionTestUtils提供了一些方便的工具,可以以最小的工作量帮助解决这个问题。它被描述为“用于单元和集成测试场景”。

在非测试上下文中,还有一个类似的类,名为ReflectionUtils,但它被描述为“仅供内部使用”——请参阅下面的回答,以更好地解释它的含义。

要解决原文中的例子:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");

其他回答

Java 9引入了变量句柄。您可以使用它们访问类的私有字段。

示例代码如下所示:

var lookup = MethodHandles.lookup();
var handle = MethodHandles
    .privateLookupIn(IWasDesignedPoorly.class, lookup)
    .findVarHandle(IWasDesignedPoorly.class, "stuffIWant", Hashtable.class);
var value = handle.get(obj);

使用Lookup和VarHandle对象作为静态final字段也是可取的。

使用Java中的反射,你可以访问一个类的所有私有/公共字段和方法到另一个类中。但是根据Oracle文档的章节缺陷,他们建议:

由于反射允许代码执行在非反射代码中不合法的操作,例如访问私有字段和方法,因此使用反射可能导致意想不到的副作用,这可能导致代码功能失调,并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为。”

下面是演示反射基本概念的代码片段

Reflection1.java

public class Reflection1{

    private int i = 10;

    public void methoda()
    {

        System.out.println("method1");
    }
    public void methodb()
    {

        System.out.println("method2");
    }
    public void methodc()
    {

        System.out.println("method3");
    }

}

Reflection2.java

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


public class Reflection2{

    public static void main(String ar[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        Method[] mthd = Reflection1.class.getMethods(); // for axis the methods 

        Field[] fld = Reflection1.class.getDeclaredFields();  // for axis the fields  

        // Loop for get all the methods in class
        for(Method mthd1:mthd)
        {

            System.out.println("method :"+mthd1.getName());
            System.out.println("parametes :"+mthd1.getReturnType());
        }

        // Loop for get all the Field in class
        for(Field fld1:fld)
        {
            fld1.setAccessible(true);
            System.out.println("field :"+fld1.getName());
            System.out.println("type :"+fld1.getType());
            System.out.println("value :"+fld1.getInt(new Reflaction1()));
        }
    }

}

希望能有所帮助。

尝试绕过这种情况下的问题,您想要设置/获取数据的类是您自己的类之一。

只需为此创建一个公共setter(Field f, Object value)和公共Object getter(Field f)。您甚至可以在这些成员函数中自己进行一些安全检查。例如,对于setter:

class myClassName {
    private String aString;

    public set(Field field, Object value) {
        // (A) do some checkings here  for security

        // (B) set the value
        field.set(this, value);
    }
}

当然,现在你必须在设置字段值之前为sString找到java.lang.reflect.Field。

我确实在一个通用的resultset -to-and-from-model- mapping器中使用了这种技术。

反射不是解决问题的唯一方法(它是访问类/组件的私有功能/行为)

另一种解决方案是从.jar中提取类,使用(比如)Jode或Jad反编译它,更改字段(或添加一个访问器),然后根据原始的.jar重新编译它。然后在类路径中将新的.class放在.jar的前面,或者将其重新插入到.jar中。(jar工具允许你提取和重新插入到一个现有的。jar)

如下所述,这解决了访问/更改私有状态的更广泛问题,而不是简单地访问/更改字段。

当然,这要求.jar不能被签名。

如果使用Spring:

在测试上下文中,ReflectionTestUtils提供了一些方便的工具,可以以最小的工作量帮助解决这个问题。它被描述为“用于单元和集成测试场景”。

在非测试上下文中,还有一个类似的类,名为ReflectionUtils,但它被描述为“仅供内部使用”——请参阅下面的回答,以更好地解释它的含义。

要解决原文中的例子:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");