代码之家  ›  专栏  ›  技术社区  ›  Frank Krueger

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

  •  428
  • Frank Krueger  · 技术社区  · 15 年前

    我在第三方有一个设计不好的班 JAR 我需要进入其中一个 私有的 领域。例如, 为什么我需要选择私人领域是必要的?

    class IWasDesignedPoorly {
        private Hashtable stuffIWant;
    }
    
    IWasDesignedPoorly obj = ...;
    

    我如何利用反射来获得 stuffIWant ?

    11 回复  |  直到 6 年前
        1
  •  592
  •   oxbow_lakes    15 年前

    为了访问私有字段,需要从类的 宣布 字段,然后使其可访问:

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

    编辑 :已由评论 无尾猿 ,同时访问字段、将其设置为可访问并检索值都将引发 Exception S,尽管只有 选中的 您需要注意的例外情况在上面进行了评论。

    这个 NoSuchFieldException 如果您请求的字段名称与声明的字段不对应,则将引发。

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

    这个 IllegalAccessException 如果字段不可访问(例如,如果该字段是私有的,并且没有通过遗漏 f.setAccessible(true) 线。

    这个 RuntimeException 可以抛出的是 SecurityException S(如果JVM的 SecurityManager 不允许更改字段的可访问性),或 IllegalArgumentException s,如果您尝试访问一个非字段类类型的对象上的字段:

    f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
    
        2
  •  124
  •   yegor256    11 年前

    尝试 FieldUtils 来自Apache Commons-Lang3:

    FieldUtils.readField(object, fieldName, true);
    
        3
  •  24
  •   Brian Agnew    7 年前

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

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

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

    这需要 罐子 当然不需要签名。

        4
  •  16
  •   Brian Agnew    12 年前

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

    def obj = new IWasDesignedPoorly()
    def hashTable = obj.getStuffIWant()
    
        5
  •  7
  •   Simmant    10 年前

    使用 Java中的反射 您可以访问 private/public 一个类到另一个类的字段和方法。 甲骨文公司 documentation 在本节中 弊端 他们建议:

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

    下面的代码快照演示了 反射

    反射1.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");
        }
    
    }
    

    反射2.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()));
            }
        }
    
    }
    

    希望能有所帮助。

        6
  •  5
  •   Laurence Gonsalves    15 年前

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

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

        7
  •  4
  •   pcpratts    15 年前

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

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

        8
  •  2
  •   Luke Hutchison    8 年前

    您需要执行以下操作:

    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);
    }
    
        9
  •  1
  •   xtof54    9 年前

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

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

        10
  •  0
  •   Steve Chambers    7 年前

    如果使用弹簧, ReflectionTestUtils 提供了一些方便的工具,可以用最少的努力来帮助解决问题。它被描述为 “用于单元和集成测试方案” . 还有一个类似的类,名为 ReflectionUtils 但这被描述为 “仅供内部使用” -见 this answer 为了解释这意味着什么。

    要处理已发布的示例:

    Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
    
        11
  •  0
  •   Scott    6 年前

    你可以使用 Manifold's @越狱 对于直接、类型安全的Java反射:

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

    @JailBreak 解锁 foo 编译器中用于直接访问中所有成员的局部变量 Foo 层次结构。

    同样,您可以使用 JavaRead() 一次性使用的扩展方法:

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

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

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

    了解更多关于 Manifold .