如何从Java中的其他类读取私有字段的值?


481

我在第三方设计的课程很差JAR,我需要访问其一个私有字段。例如,为什么我需要选择私有字段?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

如何使用反射获取值stuffIWant

Answers:


665

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

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

编辑:正如aperkins所说,访问字段,将字段设置为可访问并获取值都可能引发Exceptions,尽管上面需要注释的唯一检查异常。

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

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

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

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

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

4
您能解释一下异常注释吗?该代码将运行,但会引发异常吗?否则代码可能会引发异常?
尼尔·

1
@Nir-否-极有可能代码可以正常运行(因为默认的SecurityManager将允许更改字段的可访问性)-但您必须处理已检查的异常(捕获它们或声明它们被重新抛出)。我已经稍微修改了答案。编写一个小的测试用例进行测试,看看会发生什么,这可能对您有好处
oxbow_lakes 2009年

3
抱歉,这是我的答案。也许显示一个典型异常处理的例子。似乎当类未正确连接在一起时会发生异常。该代码示例使异常似乎在相应的llnes处引发。
LaFayette

2
getDeclaredField()如果在父类中定义了字段,则不会找到字段-您还必须遍历父类层次结构,getDeclaredField()对每个父类进行调用,直到找到匹配项(之后可以调用setAccessible(true))或到达Object
卢克·哈奇森

1
@legend,您可以安装安全管理器以禁止此类访问。从Java 9开始,这种访问不应跨模块边界进行(除非显式打开了一个模块),尽管在执行新规则方面存在过渡阶段。除此之外,需要诸如反射之类的额外工作总是会对非private领域产生影响。它可以防止意外访问。
Holger

159

FieldUtils从apache commons-lang3 尝试:

FieldUtils.readField(object, fieldName, true);

76
我相信您可以通过将commons-lang3中的几种方法组合在一起来解决世界上大多数问题。
卡梅隆

@yegor为什么Java允许这样做?意味着为什么Java允许访问私有成员?
Asif Mushtaq

1
@ yegor256我仍然可以访问C#和C ++私有成员。然后?所有语言的创造者都很弱吗?
Asif Mushtaq

3
@UnKnown java没有。有两种不同的东西-java语言和java作为java虚拟机。后者对字节码进行操作。并且有一些库来操纵它。因此,java语言不允许您在范围之外使用私有字段,或者不允许对最终引用进行变异。但是使用该工具可以做到这一点。恰好在标准库中。
evgenii '16

3
@UnKnown原因之一是Java没有“朋友”访问权限,只能通过DI,ORM或XML / JSON序列化程序之类的基础结构框架来访问字段。这样的框架需要访问对象字段以正确地序列化或初始化对象的内部状态,但是您仍然可能需要对业务逻辑进行适当的编译时封装实施。
伊万·加梅尔

26

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

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

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

.jar当然,这需要不签名。


36
对于一个简单的领域,这种方式将是非常痛苦的。
Valentin Rocher

1
我不同意。它允许您不仅访问您的字段,而且如果访问该字段还不够,还可以根据需要更改类。
布赖恩·阿格纽

4
然后,您必须再次执行该操作。如果罐子得到更新并且您对不再存在的字段使用反射会怎样?这是完全一样的问题。您只需要管理它。
Brian Agnew 2012年

4
考虑到a)突出显示它是一种实用的替代方法,b)它满足了更改字段可见性不足的情况,我感到非常惊讶
Brian Agnew

2
@BrianAgnew也许只是语义上的,但是如果我们坚持这个问题(使用反射来读取私有字段),那么不使用反射就马上成为自相矛盾。但是,我同意您提供对该字段的访问权限...但是该字段仍然不再是私有的,因此我们不再坚持该问题的“读取私有字段”。从另一个角度来看,.jar修改在某些情况下可能无法工作(带符号的jar),每次更新jar时都需要执行,需要对classpath进行仔细的操作(如果在应用容器)等
Remi Morin 2014年

18

尚未提及的另一个选择:使用Groovy。Groovy允许您访问私有实例变量,这是该语言设计的副作用。不管您在该领域是否有吸气剂,您都可以使用

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()

4
OP特别要求Java
Jochen

3
如今,许多Java项目都包含了常规功能。该项目使用Spring的groovy DSL就足够了,例如,它们在类路径上也会具有groovy。在这种情况下,此答案很有用,虽然不直接回答OP,但对许多访问者来说将是有益的。
阿米尔·阿比里

10

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

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

这是下面的代码快照,以演示反射的基本概念

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

}

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

}

希望它会有所帮助。


5

正如oxbow_lakes所提到的,您可以使用反射来解决访问限制(假设您的SecurityManager可以让您)。

就是说,如果此类设计得如此糟糕以至于使您诉诸于此类黑客,那么也许您应该寻找替代方案。当然,这个小技巧现在可能可以为您节省几个小时,但是您要付出多少代价呢?


3
我比实际要幸运的多,我只是使用这段代码来提取一些数据,之后我可以将其扔回到回收站中。
Frank Krueger

2
那么在这种情况下,请砍掉。:-)
劳伦斯·贡萨尔维斯

3
这不能为问题提供答案。要批评或要求作者澄清,请在其帖子下方发表评论。
Mureinik 2014年

@Mureinik-它确实回答了这个问题,并带有“可以使用反射”字样。它缺少示例或任何更大的解释或方法,但这是一个答案。如果您不喜欢,请对其进行投票。
ArtOfWarfare


3

您需要执行以下操作:

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

2

如果使用Spring,则ReflectionTestUtils提供了一些方便的工具,可以在不费吹灰之力的情况下为您提供帮助。它被描述为“用于单元和集成测试方案”。还有一个类似的类,名为ReflectionUtils,但是被描述为“仅供内部使用” -有关含义的解释,请参见此答案

要解决发布的示例:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");

1
如果您决定在Spring之前使用Utils类,那么您实际上应该使用非测试类(docs.spring.io/spring-framework/docs/current/javadoc-api/org/…),除非您当然要这样做实际上是将其用于单元测试。
同步

可能的是,尽管该类被描述为“仅供内部使用”。我已经在答案中添加了一些信息。(ReflectionTestUtils根据我的经验,一直使用该示例,在测试情况下,我只需要做这种事情即可。)
史蒂夫·钱伯斯

1

只是关于反射的附加说明:我观察到在某些特殊情况下,当不同包中存在多个具有相同名称的类时,在最上面的答案中使用的反射可能无法从对象中选择正确的类。因此,如果您知道对象的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);

(这是对我不起作用的示例类)


1

您可以使用Manifold的 @JailBreak进行直接的类型安全的Java反射:

@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;

public class Foo {
    private String stuffIWant;
}

@JailBreakfoo在编译器中解锁局部变量,以直接访问Foo层次结构中的所有成员。

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

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

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

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

发现有关歧管的更多信息。


1

使用XrayInterface工具非常容易。只需定义缺少的getter / setter,例如

interface BetterDesigned {
  Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}

并对您设计不良的项目进行X射线检查:

IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());

在内部,这依赖于反射。


0

尝试解决该问题,您要设置/获取数据的数据量是您自己的类之一。

只要创建一个public setter(Field f, Object value)public Object getter(Field f)为。您甚至可以在这些成员函数中自行进行一些安全检查。例如,二传手:

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

当然,现在你必须找出java.lang.reflect.FieldsString在设置字段值之前。

我确实在通用的ResultSet到模型生成器中使用了此技术。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.