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

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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


当前回答

您可以使用jOOR来实现这一点。

class Foo {
    private final String value = "ABC";
}
class Bar {
    private final Foo foo = new Foo();
    public String value() {
        return org.joor.Reflect
            .on(this.foo)
            .field("value")
            .get();
    }
}
class BarTest {
    @Test
    void accessPrivateField() {
        Assertions.assertEquals(new Bar().value(), "ABC");
    }
}

其他回答

正如oxbow_lakes提到的,您可以使用反射来绕过访问限制(假设您的SecurityManager允许您)。

也就是说,如果这门课设计得如此糟糕,以至于你不得不求助于这样的伎俩,也许你应该寻找另一种选择。当然,这个小技巧现在可能会为你节省几个小时,但它会让你付出多少代价呢?

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

(这是一个不适合我的例子)

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

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

EDIT:正如aperkins注释的那样,访问字段、将其设置为可访问和检索值都可以抛出异常,尽管您需要注意的唯一检查异常是上面注释的。

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

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

如果字段不可访问(例如,如果它是私有的,并且没有通过遗漏f.setAccessible(true)行进行访问,则抛出IllegalAccessException异常。

可能抛出的runtimeexception是securityexception(如果JVM的SecurityManager不允许你改变字段的可访问性),或者illegalargumentexception,如果你试图访问一个不属于字段类类型的对象上的字段:

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

您需要完成以下操作:

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

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

Soot完全是用Java编写的,并且适用于新的Java版本。