我在第三方JAR中有一个设计很差的类,我需要访问它的一个私有字段。例如, 为什么我需要选择私人领域是必要的?
class IWasDesignedPoorly {
private Hashtable stuffIWant;
}
IWasDesignedPoorly obj = ...;
如何使用反射来获取stuffIWant的值?
我在第三方JAR中有一个设计很差的类,我需要访问它的一个私有字段。例如, 为什么我需要选择私人领域是必要的?
class IWasDesignedPoorly {
private Hashtable stuffIWant;
}
IWasDesignedPoorly obj = ...;
如何使用反射来获取stuffIWant的值?
当前回答
尝试绕过这种情况下的问题,您想要设置/获取数据的类是您自己的类之一。
只需为此创建一个公共setter(Field f, Object value)和公共Object getter(Field f)。您甚至可以在这些成员函数中自己进行一些安全检查。例如,对于setter:
class myClassName {
private String aString;
public set(Field field, Object value) {
// (A) do some checkings here for security
// (B) set the value
field.set(this, value);
}
}
当然,现在你必须在设置字段值之前为sString找到java.lang.reflect.Field。
我确实在一个通用的resultset -to-and-from-model- mapping器中使用了这种技术。
其他回答
反射不是解决问题的唯一方法(它是访问类/组件的私有功能/行为)
另一种解决方案是从.jar中提取类,使用(比如)Jode或Jad反编译它,更改字段(或添加一个访问器),然后根据原始的.jar重新编译它。然后在类路径中将新的.class放在.jar的前面,或者将其重新插入到.jar中。(jar工具允许你提取和重新插入到一个现有的。jar)
如下所述,这解决了访问/更改私有状态的更广泛问题,而不是简单地访问/更改字段。
当然,这要求.jar不能被签名。
为了访问私有字段,你需要从类声明的字段中获取它们,然后使它们可访问:
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
还有一个没有提到的选项:使用Groovy。Groovy允许您访问私有实例变量,这是该语言设计的一个副作用。无论是否有字段的getter,都可以使用
def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
关于反射的另一个注意事项:我在一些特殊情况下观察到,当不同的包中存在几个同名的类时,顶部答案中使用的反射可能无法从对象中选择正确的类。所以如果你知道对象的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);
(这是一个不适合我的例子)
正如oxbow_lakes提到的,您可以使用反射来绕过访问限制(假设您的SecurityManager允许您)。
也就是说,如果这门课设计得如此糟糕,以至于你不得不求助于这样的伎俩,也许你应该寻找另一种选择。当然,这个小技巧现在可能会为你节省几个小时,但它会让你付出多少代价呢?