我在第三方JAR中有一个设计糟糕的类,我需要访问它的一个私有字段。例如,为什么我需要选择私人领域是必要的?
1 2 3 4 5
| class IWasDesignedPoorly {
private Hashtable stuffIWant ;
}
IWasDesignedPoorly obj = ... ; |
如何使用反射来获取stuffIWant的值?
为了访问私有字段,需要从类的声明字段中获取它们,然后使它们可访问:
1 2 3
| Field f = obj. getClass(). getDeclaredField("stuffIWant"); //NoSuchFieldException
f. setAccessible(true);
Hashtable iWantThis = (Hashtable) f. get(obj ); //IllegalAccessException |
编辑:正如aperkins所评论的那样,访问字段、将其设置为可访问并检索值都将抛出Exception,尽管您需要注意的唯一选中的异常是上面所评论的。
如果您要求字段的名称与声明的字段不对应,那么将抛出NoSuchFieldException。
1
| obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException |
如果字段不可访问(例如,如果它是私有的,并且没有通过缺少f.setAccessible(true)行来访问),则会抛出IllegalAccessException。
可以抛出的RuntimeException是SecurityExceptions(如果JVM的SecurityManager不允许您更改字段的可访问性),或者是IllegalArgumentExceptions(如果您尝试在非字段类类型的对象上访问字段):
1
| f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type |
- 另外请注意,您必须从f.get中捕获一两个异常。
- 你能解释一下例外评论吗?代码将运行,但会引发异常?或者代码可能会抛出异常?
- @NIR-否-很可能代码运行正常(默认的SecurityManager将允许更改字段的可访问性),但您必须处理检查过的异常(捕获它们或声明它们是重新拥有的)。我修改了我的答案。编写一个小的测试用例来四处游玩,看看会发生什么,这可能对您有好处。
- @NIR——正是Oxbow说的。代码将检查有关获取声明字段和获取对象值的异常。你只需要确保处理这些案件。
- 非常有用的答案。我不小心被派到教室里去了。>静态对象而不是实例。在静电场中工作得很好,但我对其余的问题感到困惑:—)
- a.setAccessible(true); String var = a.getName(); a.get(var)给了我Can not set final to java.lang.String。
- 对不起,这是我很困惑的答案。可能会显示一个典型异常处理的例子。当类错误地连接在一起时,似乎会发生异常。代码示例使异常看起来像是被抛出到相应的LLNE中。
- 如果字段是在父类中定义的,那么getDeclaredField()就找不到字段——您还必须遍历父类层次结构,对每个类调用getDeclaredField(),直到找到匹配项(之后您可以调用setAccessible(true))或到达Object。
- @Oxbow_Lakes并没有违反私有变量的原始语义。那么,私人的目的是什么呢?如果我们仍然可以通过反思来改变它。
- @图例您可以安装安全管理器以禁止此类访问。由于Java 9,这样的访问不应该跨模块边界工作(除非模块显式打开),尽管在执行新规则方面存在过渡阶段。除此之外,需要额外的工作,如反射,总是对非private字段产生影响。防止意外进入。
- 在jdk 9+/jpms中,如果非公共字段与运行反射代码的类(y的模块)在不同的模块(x中,则x必须向y打开(opens本身),以便f.setAccessible(true)成功。
从Apache Commons-Lang3试用FieldUtils:
1
| FieldUtils.readField(object, fieldName, true); |
- 我相信您可以通过将Commons-Lang3中的一些方法串在一起来解决世界上的大多数问题。
- 叶戈尔为什么Java允许这样做?为什么Java允许访问私有成员?
- @ YEGOR256,我仍然可以访问C和C + +的私人成员太…然后呢?所有语言的创建者都很弱?
- @未知的Java没有。有两种不同的东西——Java语言和Java作为Java虚拟机。后者操作字节码。还有一些库可以操纵它。因此,Java语言不允许您在范围之外使用私有字段,或者不允许更改最终引用。但是使用这个工具是可以做到的。正好在标准库中。
- @叶甫根尼莫罗佐夫又问了为什么。为什么Java允许使用或创建该工具?
- "未知",因为绝大多数Java社区都不知道好的编程实践是什么。他们只是转换C/C++程序员,他们正好为建筑软件付费。"一切可行"都是他们的理念。这就是Java允许这种访问的原因。
- @是256,你的意思是我们不能访问C私有字段?stackoverflow.com/questions/95910/…我认为即使是一个愚蠢的程序员也不会写代码来访问私有字段(没有任何原因)。
- @不知道,你为什么不问问为什么有反汇编程序,它可以破解任何软件?Java并不真正允许任何事情。阻止人们这样做是不可能的。如果Sun不写这样一个库来操作字节码,其他人也会写。这只是时间的问题,一点好奇,一点智慧——大多数的东西都意味着成为一个真正的黑客(说起来有点老套)。
- @是的,256,我很肯定你知道"黑客"的定义很少。拥有非常独特的工具完全与良好的设计是正交的,这是主观的,取决于…
- @Evgenii我不是在说源代码外的工具。为什么Java创建反射API来访问私有成员?我不明白为什么你不试着证明所有的语言创建者都很软弱,而不是提供真正的答案。(一次又一次,我不是在谈论外包工具)
- "不知道,他们都是人,我们(人)犯错误,但我很怀疑这就是为什么他们在Java中创建反射。这对詹姆斯·戈斯林来说可能是个好问题。
- "未知"的一个原因是Java没有"朋友"访问权限,只能通过诸如DI、ORM或XML/JSON序列化的基础架构框架来访问该字段。这些框架需要访问对象字段以正确地序列化或初始化对象的内部状态,但您可能仍然需要为业务逻辑执行适当的编译时封装。
- 可以通过Java安全策略禁止反射。
反射并不是解决问题的唯一方法(即访问类/组件的私有功能/行为)。
另一种解决方案是从.jar中提取类,使用jode或jad对其进行解压,更改字段(或添加一个访问器),然后根据原始的.jar重新编译它。然后将新的.class放在类路径中.jar之前,或者将其重新插入.jar中。(jar实用程序允许您提取并重新插入现有的.jar)
如下文所述,这解决了访问/更改私有状态的更广泛问题,而不是简单地访问/更改字段。
当然,这要求不签署.jar。
- 对于一个简单的字段,这样做会非常痛苦。
- 我不同意。它不仅允许您访问字段,而且允许您在访问字段不充分时,在必要时更改类。
- 当jar得到更新时会发生什么?;)
- 然后你必须再做一次。如果jar得到一个更新,并且您对不再存在的字段使用反射,会发生什么?这完全是同一个问题。你只需要管理它。
- 我很惊讶这会被否决的程度给定a)它被强调为一个实用的替代方案b)它迎合的场景中改变一个领域的可见性是不够的。
- BrianAgnew,尽管你是对的,确切的问题是"我如何阅读Java中的私有字段?"您的答案是"如何从现有jar中的字段中删除private",我想这就是为什么您被低估的原因。
- "jar实用程序允许您提取并重新插入现有的.jar",或者更简单地说,只需使用7-zip打开jar,或者将.jar重命名为.zip并使用Windows资源管理器打开。
- @Remimorin-"更改字段(或添加访问器)"在我看来是正确的
- @brianagenew也许只是语义上的问题,但如果我们坚持这个问题(使用反射来阅读一个私有领域),不使用反射就立刻成为一种自相矛盾的现象。但我同意你提供进入这个领域的途径…但是这个领域仍然不再是私有的,所以我们不再坚持这个问题的"阅读私有领域"。从另一个角度来看,.jar修改在某些情况下可能不起作用(有符号的jar),每次更新jar时都需要进行修改,需要小心操作类路径(如果在应用程序容器中执行,则可能无法完全控制类路径)等。
- 我认为它可能被忽略了,你不能这样读一个值stuffIWant,所以这不能回答问题。
- +1代表备选方案。我已经做了很多次了。尽管你可能并不总是需要把它重新插入罐子里。大多数时候,我只是将修改后的类插入到我自己项目中的另一个包中,并将JAR作为依赖项来保存,这样类就可以编译(如果它有来自该JAR的任何依赖项——它们通常是这样做的)。如果您的IDE有自己的反编译器,那么这个过程需要花费几秒钟的时间来执行。
还有一个尚未提到的选项:使用groovy。groovy允许您访问私有实例变量,作为语言设计的副作用。无论你是否有一个场上的盖特,你都可以用
1 2
| def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant() |
- OP专门要求Java
- 现在许多Java项目都包含Groovy。项目使用Spring的groovyDSL就足够了,例如,他们将在类路径上使用groovy。在这种情况下,这个答案是有用的,虽然没有直接回答OP,但对许多访客来说是有益的。
使用爪哇中的反射,您可以访问一个类的所有EDCOX1、12个字段和方法到另一个类。但是,根据Oracle文档中的缺陷部分,他们建议:
"由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,因此反射的使用可能导致意外的副作用,这可能导致代码功能不正常,并可能破坏可移植性。反射代码会破坏抽象,因此可能会随着平台的升级而改变行为。"
下面是演示反射的基本概念的代码快照
反射1.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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 ()));
}
}
} |
希望能有所帮助。
正如Oxbow-Lakes提到的,您可以使用反射来绕过访问限制(假设您的安全管理器允许您这样做)。
这就是说,如果这个类的设计太差,以至于你求助于这种黑客,也许你应该寻找一种替代方法。当然,这个小黑客现在可能会帮你节省几个小时,但在路上要花多少钱呢?
- 实际上,我比这更幸运,我只是用这个代码提取一些数据,然后我可以把它扔回回收站。
- 好吧,在这种情况下,黑客离开。-)
- 这并不能回答这个问题。要评论或要求作者澄清,请在他们的帖子下面留下评论。
- @穆雷尼克-它的确回答了这个问题,用"你可以使用反射"这个词。它缺乏一个例子或任何更大的解释或如何解释,但它是一个答案。如果你不喜欢就投反对票。
使用SOOT Java优化框架直接修改字节码。http://www.sable.mcgill.ca/smook/
SOOT完全用Java编写,并与新的Java版本一起工作。
您需要执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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 );
} |
关于反射的另外一个注意事项:我在一些特殊情况下观察到,当不同的包中存在多个同名的类时,顶部答案中使用的反射可能无法从对象中选择正确的类。因此,如果您知道对象的package.class是什么,那么最好访问它的私有字段值,如下所示:
1 2 3 4
| 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 ); |
(这是不适合我的示例类)
你可以使用歧管的直接"越狱,类型安全的Java反射:
1 2 3 4 5 6
| @JailBreak Foo foo = new Foo();
foo.stuffIWant ="123;
public class Foo {
private String stuffIWant;
} |
@JailBreakunlocks《foo局部变量在编译程序直接访问所有的成员在foo的层次。
你可以使用similarly《越狱(方法)推广一次性使用。
1
| foo.jailbreak().stuffIWant ="123"; |
jailbreak()通的方法,你可以访问任何成员在foo的层次。
在这两个案例resolves编译《现场访问你的类型安全的,仿佛一场公共generates流形,但高效的代码为你在反射罩。
发现更多关于流形。
如果使用跳跃,reflectiontestutils提供一些方便的工具,帮助在外面用最小的努力。它的described AA有"的使用在单元和集成测试的情景。"也有一个类似的类,但这是"reflectionutils described为"只用于内部使用"看这个答案的解释这是什么意思。
地址发布的两个实例:
- 如果您决定在Spring中使用utils类,那么您应该使用非测试类(docs.spring.io/spring framework/docs/current/javadoc api/或&zwnj;&35; 8203;g/&hellip;)来代替,当然,除非您实际上在单元测试中使用它。
- 可能,尽管该类被描述为"仅用于内部使用"。我在答案中添加了一些关于这个的信息。(保留了使用ReflectionTestUtils的示例,因为根据我的经验,我只需要在测试环境中做这种事情。)