首页 文章

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

提问于
浏览
405

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

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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

11 回答

  • 0

    您可以使用Manifold's @JailBreak 进行直接的,类型安全的Java反射:

    @JailBreak Foo foo = new Foo();
    foo.stuffIWant = "123;
    
    public class Foo {
        private String stuffIWant;
    }
    

    @JailBreak 解锁编译器中的 foo 局部变量,以便直接访问 Foo 层次结构中的所有成员 .

    同样,您可以使用 jailbreak() 扩展方法进行一次性使用:

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

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

    在这两种情况下,编译器都会为您安全地键入字段访问,就像公共字段一样,而Manifold会为您生成有效的反射代码 .

    了解有关Manifold的更多信息 .

  • 23

    使用Soot Java Optimization框架直接修改字节码 . http://www.sable.mcgill.ca/soot/

    Soot完全用Java编写,适用于新的Java版本 .

  • 16

    关于反射的另一个注意事项:我在一些特殊情况下观察到,当不同包中存在多个具有相同名称的类时,顶部答案中使用的反射可能无法从对象中选择正确的类 . 因此,如果您知道对象的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);
    

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

  • 572

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

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

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

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

  • 117

    从apache commons-lang3尝试FieldUtils

    FieldUtils.readField(object, fieldName, true);
    
  • 7

    您需要执行以下操作:

    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);
    }
    
  • 4

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

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

    EDIT :正如aperkins所评论的那样,两者都访问该字段,将其设置为可访问并检索该值将全部抛出 Exception ,尽管您需要注意的唯一已检查的异常在上面进行了评论 .

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

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

    如果该字段不可访问,则会抛出 IllegalAccessException (例如,如果该字段是私有的,并且由于错过了 f.setAccessible(true) 行而无法访问该字段 .

    可能抛出的 RuntimeExceptionSecurityException s(如果JVM的 SecurityManager 将不允许您更改字段的可访问性),或者 IllegalArgumentException s,如果您尝试访问非字段's class'类型的对象上的字段:

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

    另一个尚未提及的选项:使用 Groovy . Groovy允许您访问私有实例变量,作为语言设计的副作用 . 无论你是否有野外吸气器,你都可以使用

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

    使用 Reflection in Java ,您可以访问一个类的所有 private/public 字段和方法到另一个类 . 但是根据 drawbacks 中的 Oracle documentation,他们建议:

    "Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform"

    这是以下代码snapts来演示 Reflection 的基本概念

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

    希望它会有所帮助 .

  • 0

    正如oxbow_lakes所提到的,你可以使用反射来绕过访问限制(假设你的SecurityManager会让你) .

    也就是说,如果这个类的设计非常糟糕以至于让你诉诸于这样的hackery,那么也许你应该寻找替代方案 . 当然,这个小小的黑客现在可能会为你节省几个小时,但是你需要花多少钱呢?

  • 5

    如果使用Spring,ReflectionTestUtils提供了一些方便的工具,只需要很少的努力就可以提供帮助 . 它被描述为"for use in unit and integration testing scenarios" . 还有一个名为ReflectionUtils的类似的类,但这被描述为"Only intended for internal use" - 请参阅this answer以解释这意味着什么 .

    要解决发布的示例:

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

相关问题