如何从Java中的不同类读取私有字段的值?

How to read the value of a private field from a different class in Java?

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

1
2
3
4
5
class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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


为了访问私有字段,需要从类的声明字段中获取它们,然后使它们可访问:

1
2
3
Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException

编辑:正如aperkins所评论的那样,访问字段、将其设置为可访问并检索值都将抛出Exception,尽管您需要注意的唯一选中的异常是上面所评论的。

如果您要求字段的名称与声明的字段不对应,那么将抛出NoSuchFieldException

1
obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException

如果字段不可访问(例如,如果它是私有的,并且没有通过缺少f.setAccessible(true)行来访问),则会抛出IllegalAccessException

可以抛出的RuntimeExceptionSecurityExceptions(如果JVM的SecurityManager不允许您更改字段的可访问性),或者是IllegalArgumentExceptions(如果您尝试在非字段类类型的对象上访问字段):

1
f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type


从Apache Commons-Lang3试用FieldUtils

1
FieldUtils.readField(object, fieldName, true);


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

另一种解决方案是从.jar中提取类,使用jode或jad对其进行解压,更改字段(或添加一个访问器),然后根据原始的.jar重新编译它。然后将新的.class放在类路径中.jar之前,或者将其重新插入.jar中。(jar实用程序允许您提取并重新插入现有的.jar)

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

当然,这要求不签署.jar


还有一个尚未提到的选项:使用groovy。groovy允许您访问私有实例变量,作为语言设计的副作用。无论你是否有一个场上的盖特,你都可以用

1
2
def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()


使用爪哇中的反射,您可以访问一个类的所有EDCOX1、12个字段和方法到另一个类。但是,根据Oracle文档中的缺陷部分,他们建议:

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

下面是演示反射的基本概念的代码快照

反射1.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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");
    }

}

反射2.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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()));
        }
    }

}

希望能有所帮助。


正如Oxbow-Lakes提到的,您可以使用反射来绕过访问限制(假设您的安全管理器允许您这样做)。

这就是说,如果这个类的设计太差,以至于你求助于这种黑客,也许你应该寻找一种替代方法。当然,这个小黑客现在可能会帮你节省几个小时,但在路上要花多少钱呢?


使用SOOT Java优化框架直接修改字节码。http://www.sable.mcgill.ca/smook/

SOOT完全用Java编写,并与新的Java版本一起工作。


您需要执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static Field getField(Class<?> cls, String fieldName) {
    for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
        try {
            final Field field = c.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        } catch (final NoSuchFieldException e) {
            // Try parent
        } catch (Exception e) {
            throw new IllegalArgumentException(
                   "Cannot access field" + cls.getName() +"." + fieldName, e);
        }
    }
    throw new IllegalArgumentException(
           "Cannot find field" + cls.getName() +"." + fieldName);
}

关于反射的另外一个注意事项:我在一些特殊情况下观察到,当不同的包中存在多个同名的类时,顶部答案中使用的反射可能无法从对象中选择正确的类。因此,如果您知道对象的package.class是什么,那么最好访问它的私有字段值,如下所示:

1
2
3
4
org.deeplearning4j.nn.layers.BaseOutputLayer ll = (org.deeplearning4j.nn.layers.BaseOutputLayer) model.getLayer(0);
Field f = Class.forName("org.deeplearning4j.nn.layers.BaseOutputLayer").getDeclaredField("solver");
f.setAccessible(true);
Solver s = (Solver) f.get(ll);

(这是不适合我的示例类)


你可以使用歧管的直接"越狱,类型安全的Java反射:

1
2
3
4
5
6
@JailBreak Foo foo = new Foo();
foo.stuffIWant ="123;

public class Foo {
    private String stuffIWant;
}

@JailBreakunlocks《foo局部变量在编译程序直接访问所有的成员在foo的层次。

你可以使用similarly《越狱(方法)推广一次性使用。

1
foo.jailbreak().stuffIWant ="123";

jailbreak()通的方法,你可以访问任何成员在foo的层次。

在这两个案例resolves编译《现场访问你的类型安全的,仿佛一场公共generates流形,但高效的代码为你在反射罩。

发现更多关于流形。


如果使用跳跃,reflectiontestutils提供一些方便的工具,帮助在外面用最小的努力。它的described AA有"的使用在单元和集成测试的情景。"也有一个类似的类,但这是"reflectionutils described为"只用于内部使用"看这个答案的解释这是什么意思。

地址发布的两个实例:

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