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

Fra*_*ger 466 java reflection field private class

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

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;
Run Code Online (Sandbox Code Playgroud)

我如何使用反射来获得价值stuffIWant

oxb*_*kes 649

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

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

编辑:正如aperkins评论的那样,访问该字段,将其设置为可访问并检索值都将抛出Exceptions,尽管上面评论了您需要注意的唯一已检查的异常.

NoSuchFieldException如果你问一个字段由不符合声明的字段的名称将被抛出.

obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException
Run Code Online (Sandbox Code Playgroud)

IllegalAccessException会如果字段是不可访问(被抛出例如,如果是私人和通过失踪了尚未作出访问f.setAccessible(true)线.

RuntimeException可抛出s为要么SecurityExceptionS(如果JVM的SecurityManager将不允许你改变一个字段的可访问性),或IllegalArgumentExceptionS,如果你尝试的对象不是字段的类的类型上访问该字段:

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

  • 你能解释一下异常评论吗?代码将运行但会抛出异常?或者代码可能抛出异常? (4认同)
  • 对不起,这个答案让我感到困惑.也许展示一个典型的异常处理的例子.当类错误地连接在一起时,似乎会发生异常.代码示例使得看起来异常将在相应的llnes处抛出. (3认同)
  • 如果在父类中定义了字段,则`getDeclaredField()`不会找到字段-您还必须遍历父类层次结构,在每个父类上调用`getDeclaredField()`,直到找到匹配项(之后您可以调用`setAccessible(true)`),或到达`Object`。 (2认同)
  • @legend您可以安装安全管理器来禁止此类访问。从 Java 9 开始,这种访问不应该跨模块边界工作(除非模块显式打开),尽管有一个关于新规则执行的过渡阶段。除此之外,需要像反射这样的额外工作总是会对非“私有”字段产生影响。它可以防止意外访问。 (2认同)

yeg*_*256 147

试试FieldUtilsapache commons-lang3:

FieldUtils.readField(object, fieldName, true);
Run Code Online (Sandbox Code Playgroud)

  • 我相信你可以通过将一些方法与commons-lang3串联起来解决世界上大多数问题. (71认同)
  • @UnKnown java没有.有两种不同的东西 - java语言和java作为java虚拟机.后者在字节码上运行.还有一些库可以操纵它.因此,java语言不允许您使用范围之外的私有字段,或者不允许更改最终引用.但是使用该工具可以做到这一点.恰好在标准库中. (3认同)
  • @UnKnown其中一个原因是Java没有"朋友"访问权限,只能通过DI,ORM或XML/JSON序列化程序等基础结构框架来访问该字段.此类框架需要访问对象字段才能正确序列化或初始化对象的内部状态,但您仍可能需要对业务逻辑进行适当的编译时封装实施. (3认同)

Bri*_*new 26

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

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

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

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

  • 这种方式对于一个简单的领域来说将是非常痛苦的. (35认同)
  • 然后你必须再做一次.如果jar获得更新并且您对不再存在的字段使用反射会发生什么?这是完全相同的问题.你只需要管理它. (4认同)
  • 考虑到a)它被强调为一种实用的替代方案,我很惊讶它是多少被投入使用b)它适用于改变场地能见度不足的场景 (4认同)
  • 我不同意。它不仅允许您访问您的字段,而且如果访问该字段还不够,还可以在必要时更改类。 (2认同)
  • @BrianAgnew也许它只是语义,但如果我们坚持这个问题(使用反射来阅读私人领域),不使用反射就会立即产生矛盾.但是我同意你提供进入该领域的权限......但是该领域仍然不再是私人的,所以我们再也不坚持问题的"阅读私人领域".从另一个角度来看,.jar修改在某些情况下可能不起作用(签名jar),需要在每次更新jar时进行,需要仔细操作类路径(如果你在一个类中执行,你可能无法完全控制)申请容器)等 (2认同)

luc*_*cas 18

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

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
Run Code Online (Sandbox Code Playgroud)

  • OP专门要求Java (4认同)
  • 今天许多Java项目都采用了groovy.这个项目使用Spring的groovy DSL就足够了,例如他们会在类路径上使用groovy.在这种情况下,这个答案是有用的,虽然不直接回答OP,但它对许多访问者都有好处. (3认同)

Sim*_*ant 8

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

"由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,使用反射会导致意外的副作用,这可能导致代码功能失常并可能破坏可移植性.反射代码打破抽象,因此可能会通过升级平台来改变行为"

下面是代码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");
    }

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

希望它会有所帮助.


Nol*_*uen 7

Java 9 引入了变量句柄。您可以使用它们访问类的私有字段。

您的示例的代码如下所示:

var lookup = MethodHandles.lookup();
var handle = MethodHandles
    .privateLookupIn(IWasDesignedPoorly.class, lookup)
    .findVarHandle(IWasDesignedPoorly.class, "stuffIWant", Hashtable.class);
var value = handle.get(obj);
Run Code Online (Sandbox Code Playgroud)

还建议使用LookupVarHandle对象作为static final字段。


Lau*_*ves 5

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

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

  • 我比实际上更幸运,我只是使用这段代码来提取一些数据,之后我可以把它扔回回收站. (3认同)
  • 这并没有提供问题的答案.要对作者进行批评或要求澄清,请在帖子下方留言. (3认同)
  • 好吧,在那种情况下,黑客攻击.:-) (2认同)