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

发布于 2024-07-30 02:10:19 字数 270 浏览 11 评论 0原文

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

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

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

I have a poorly designed class in a 3rd-party JAR and I need to access one of its private fields. For example,
why should I need to choose private field is it necessary?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

How can I use reflection to get the value of stuffIWant?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

陌若浮生 2024-08-06 02:10:20

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

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

下面的代码片段演示了 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");
    }

}

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

}

希望它会有所帮助。

Using the Reflection in Java you can access all the private/public fields and methods of one class to another .But as per the Oracle documentation in the section drawbacks they recommended that :

"Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform"

here is following code snapts to demonstrate basic concepts of 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");
    }

}

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

}

Hope it will help.

平安喜乐 2024-08-06 02:10:20

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

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

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

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

Java 9 introduced Variable Handles. You can access a private field of a class using them.

The code for your example will look like following:

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

It is also advisable to use Lookup and VarHandle objects as static final fields.

触ぅ动初心 2024-08-06 02:10:20

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

也就是说,如果这个类的设计如此糟糕,以至于让您不得不求助于这种黑客手段,也许您应该寻找替代方案。 当然,这个小窍门现在可能会为您节省几个小时,但是它会花费您多少钱呢?

As oxbow_lakes mentions, you can use reflection to get around the access restrictions (assuming your SecurityManager will let you).

That said, if this class is so badly designed that it makes you resort to such hackery, maybe you should look for an alternative. Sure this little hack might be saving you a few hours now, but how much will it cost you down the road?

我也只是我 2024-08-06 02:10:20

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

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

Just an additional note about reflection: I have observed in some special cases, when several classes with the same name exist in different packages, that reflection as used in the top answer may fail to pick the correct class from the object. So if you know what is the package.class of the object, then it's better to access its private field values as follows:

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

(This is the example class that was not working for me)

回首观望 2024-08-06 02:10:20

您需要执行以下操作:

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

You need to do the following:

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);
}
独留℉清风醉 2024-08-06 02:10:20

您可以使用 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");
    }
}

You can use jOOR for that.

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");
    }
}
零時差 2024-08-06 02:10:20

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

Soot 完全是用Java 并适用于新的 Java 版本。

Use the Soot Java Optimization framework to directly modify the bytecode.
http://www.sable.mcgill.ca/soot/

Soot is completely written in Java and works with new Java versions.

瑾夏年华 2024-08-06 02:10:20

如果使用 Spring:

测试环境中,ReflectionTestUtils 提供了一些方便的工具,可以轻松地帮助解决此问题。 它被描述为“用于单元和集成测试场景”。

非测试环境中,还有一个名为 ReflectionUtils 但这被描述为“仅供内部使用” - 请参阅这个答案可以很好地解释这意味着什么。

要解决原始帖子中的示例:

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

If using Spring:

In a testing context, ReflectionTestUtils provides some handy tools that can help out here with minimal effort. It's described as being "for use in unit and integration testing scenarios".

In a non-testing context, there is also a similar class named ReflectionUtils but this is described as "Only intended for internal use" - see this answer for a good interpretation of what this means.

To address the example in the original post:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
饭团 2024-08-06 02:10:20

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

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

然后检查您设计不佳的项目:

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

在内部,这依赖于反射。

It is quite easy with the tool XrayInterface. Just define the missing getters/setters, e.g.

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

and xray your poor designed project:

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

Internally this relies on reflection.

神爱温柔 2024-08-06 02:10:20

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

只需为此创建一个 public setter(Field f, Object value)public 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);
    }
}

当然,现在您必须在设置字段值之前找到sStringjava.lang.reflect.Field

我确实在通用结果集到模型映射器和模型映射器中使用了这种技术。

Try to go around the problem for the case, the calass of which you want to set/get data is one of your own classes.

Just create a public setter(Field f, Object value) and public Object getter(Field f) for that. You can even do some securoty check on your own inside theses member functions. E.g. for the 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);
    }
}

Of course, now you have to find out the java.lang.reflect.Field for sString prior to setting of field value.

I do use this technique in a generic ResultSet-to-and-from-model-mapper.

靖瑶 2024-08-06 02:10:19

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

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

编辑:正如aperkins所评论的那样em>,访问字段、将其设置为可访问以及检索值都可能抛出异常,尽管您需要注意的唯一已检查异常已在上面进行了注释。

如果您请求的字段名称与声明的字段不对应,则会抛出 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

In order to access private fields, you need to get them from the class's declared fields and then make them accessible:

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

EDIT: as has been commented by aperkins, both accessing the field, setting it as accessible and retrieving the value can throw Exceptions, although the only checked exceptions you need to be mindful of are commented above.

The NoSuchFieldException would be thrown if you asked for a field by a name which did not correspond to a declared field.

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

The IllegalAccessException would be thrown if the field was not accessible (for example, if it is private and has not been made accessible via missing out the f.setAccessible(true) line.

The RuntimeExceptions which may be thrown are either SecurityExceptions (if the JVM's SecurityManager will not allow you to change a field's accessibility), or IllegalArgumentExceptions, if you try and access the field on an object not of the field's class's type:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
囍笑 2024-08-06 02:10:19

尝试 FieldUtils 来自 Apache commons-lang3

FieldUtils.readField(object, fieldName, true);

PS 在我看来, 反思是邪恶的

Try FieldUtils from Apache commons-lang3:

FieldUtils.readField(object, fieldName, true);

P.S. In my opinion, reflection is evil.

迷乱花海 2024-08-06 02:10:19

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

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

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

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

Reflection isn't the only way to resolve your issue (which is to access the private functionality/behaviour of a class/component)

An alternative solution is to extract the class from the .jar, decompile it using (say) Jode or Jad, change the field (or add an accessor), and recompile it against the original .jar. Then put the new .class ahead of the .jar in the classpath, or reinsert it in the .jar. (the jar utility allows you to extract and reinsert to an existing .jar)

As noted below, this resolves the wider issue of accessing/changing private state rather than simply accessing/changing a field.

This requires the .jar not to be signed, of course.

你是年少的欢喜 2024-08-06 02:10:19

另一种尚未提及的选项:使用 Groovy。 Groovy 允许您访问私有实例变量,这是该语言设计的副作用。 无论你是否有该字段的吸气剂,你都可以使用

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

One other option that hasn't been mentioned yet: use Groovy. Groovy allows you to access private instance variables as a side effect of the design of the language. Whether or not you have a getter for the field, you can just use

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文