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

如何从Java中的不同类读取私有字段的值?,java,class,reflection,field,private,Java,Class,Reflection,Field,Private,我在第三方JAR中有一个设计糟糕的类,我需要访问它的私有字段之一。例如 为什么我需要选择私人领域是必要的 class IWasDesignedPoorly { private Hashtable stuffIWant; } IWasDesignedPoorly obj = ...; 如何使用反射来获取stuffIWant的值?要访问私有字段,需要从类的声明字段中获取它们,然后使其可访问: Field f = obj.getClass().getDeclaredField("stuff

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

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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

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

Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException
编辑:正如aperkins所评论的那样,访问字段、将其设置为可访问以及检索值都会引发
异常
s,尽管您需要注意的唯一已检查的异常已在上面进行了评论

如果您使用与声明的字段不对应的名称请求字段,则会抛出
NoSuchFieldException

obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException
如果该字段不可访问(例如,如果该字段是私有的且未通过遗漏
f.setAccessible(true)
行使其可访问,则将抛出
IllegalAccessException

可能引发的
RuntimeException
s要么是
SecurityException
s(如果JVM的
SecurityManager
不允许您更改字段的可访问性),要么是
IllegalArgumentException
s,如果您尝试访问非字段类类型的对象上的字段:

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

正如oxbow_lakes提到的,您可以使用反射绕过访问限制(假设您的安全管理器允许)


这就是说,如果这个类的设计如此糟糕,以至于你不得不求助于这种黑客行为,也许你应该寻找一种替代方法。当然,这个小黑客现在可能会为你节省几个小时,但它会花费你多少钱呢?

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

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

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


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

使用Socome Java优化框架直接修改字节码。


烟尘完全是用Java编写的,可以与新的Java版本一起使用。

还有一个尚未提及的选项:使用Groovy。Groovy允许您访问私有实例变量,这是语言设计的一个副作用。无论您是否有该字段的getter,您都可以使用

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
从apache commons-lang3尝试:

FieldUtils.readField(object, fieldName, true);

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

"由于反射允许代码执行非反射代码中非法的操作,例如访问私有字段和方法,因此使用反射可能会产生意外的副作用,这可能导致代码功能失调,并可能破坏可移植性。反射代码破坏抽象,因此可能会使用upgra更改行为“平台的安全性”

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

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()));
        }
    }

}

希望这会有所帮助。

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

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);

(这是一个不适用于我的示例类)

您需要执行以下操作:

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);
}
private静态字段getField(类cls,字符串fieldName){
for(类c=cls;c!=null;c=c.getSuperclass()){
试一试{
最终字段=c.getDeclaredField(字段名);
字段。setAccessible(true);
返回字段;
}捕获(最终NOSUCHFIELD异常e){
//试试父母
}捕获(例外e){
抛出新的IllegalArgumentException(
“无法访问字段”+cls.getName()+“+fieldName,e);
}
}
抛出新的IllegalArgumentException(
“找不到字段”+cls.getName()+“+fieldName”);
}

如果使用弹簧:

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

非测试上下文中也有一个类似的类,名为,但被描述为“仅供内部使用”-请参阅,以获得对其含义的良好解释

要解决原始帖子中的示例:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
您可以使用@JailBreak进行直接的、类型安全的Java反射:

@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;

public class Foo {
    private String stuffIWant;
}
@JailBreak
解锁编译器中的
foo
局部变量,以便直接访问
foo
层次结构中的所有成员

类似地,您可以使用越狱()扩展方法进行一次性使用:

foo.jailbreak().stuffIWant = "123";
通过
jailbreak()
方法,您可以访问
Foo
层次结构中的任何成员

在这两种情况下,编译器都会安全地解析您键入的字段访问权限,就像解析公共字段一样,而流形会在h下为您生成有效的反射代码
interface BetterDesigned {
  Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}
IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());