Java中通过反射访问私有继承字段

118

我发现可以通过 class.getDeclaredFields(); 获取继承的成员变量, 并使用 class.getFields() 访问私有成员变量, 但我正在寻找私有继承字段。 我如何实现这个?


28
“private inherited fields”不存在。如果一个字段是私有的,它就不会被继承,它只存在于父类的作用域中。 如果要访问父类的私有字段,必须先访问父类本身(参见aioobe的回答)。 - Benoit Courtine
6
话虽如此,受保护的字段是可以被继承的,但你必须通过反射来执行相同的操作才能获取它们。 - Bozho
8个回答

138

这个示例演示了如何解决这个问题:

import java.lang.reflect.Field;

class Super {
    private int i = 5;
}

public class B extends Super {
    public static void main(String[] args) throws Exception {
        B b = new B();
        Field f = b.getClass().getSuperclass().getDeclaredField("i");
        f.setAccessible(true);
        System.out.println(f.get(b));
    }
}

(或使用Class.getDeclaredFields获取所有字段的数组。)

输出:

5

这个代码会获取所有超类的字段还是仅限于直接超类? - VimNing
直接超类的字段。如果您想向上移动,可以对 getSuperclass() 进行递归,直到达到 null - aioobe
为什么您不使用getDeclaredFields()[0]getDeclaredField("i"),而是在接下来的两个语句中重复[0]数组访问? - Holger
这是由于这个特定问题的表述方式。它基本上只是一个如何使用getDeclaredFields的演示。答案已经更新。 - aioobe

47

在这里最好的方法是使用访问者模式,查找类中的所有字段和超类,并对它们执行回调操作。


实现

Spring框架有一个很好用的Utility类ReflectionUtils,它定义了一个方法可以遍历所有超类的字段并进行回调:ReflectionUtils.doWithFields()

文档:

在目标类中调用给定回调函数以处理所有字段,一直到类层次结构的顶部获取所有声明的字段。

参数:
- clazz - 目标类
- fc - 对每个字段调用的回调函数
- ff - 确定应该将回调函数应用于哪些字段的过滤器

示例代码:

ReflectionUtils.doWithFields(RoleUnresolvedList.class,
    new FieldCallback(){

        @Override
        public void doWith(final Field field) throws IllegalArgumentException,
            IllegalAccessException{

            System.out.println("Found field " + field + " in type "
                + field.getDeclaringClass());

        }
    },
    new FieldFilter(){

        @Override
        public boolean matches(final Field field){
            final int modifiers = field.getModifiers();
            // no static fields please
            return !Modifier.isStatic(modifiers);
        }
    });

输出结果:

在javax.management.relation.RoleUnresolvedList类中找到一个名为private transient boolean javax.management.relation.RoleUnresolvedList.typeSafe的字段
在javax.management.relation.RoleUnresolvedList类中找到一个名为private transient boolean javax.management.relation.RoleUnresolvedList.tainted的字段
在java.util.ArrayList类中找到一个名为private transient java.lang.Object[] java.util.ArrayList.elementData的字段
在java.util.ArrayList类中找到一个名为private int java.util.ArrayList.size的字段
在java.util.AbstractList类中找到一个名为protected transient int java.util.AbstractList.modCount的字段


4
这并不是"访问者模式",但如果你的代码中有Spring病毒,这仍然是一个非常好的工具。谢谢分享 :) - thinlizzy
3
@jose.diego 我相信你可以就此进行争论。它访问的是一个类层次结构而不是对象树,但原理仍然相同。 - Sean Patrick Floyd
不确定这个评论是否会得到回复,但是你只能用这个解决方案一次访问特定的字段。如果我需要同时查看其他字段 - 例如,如果另一个字段为空,则将此字段设置为“abc” - 我没有整个对象可用。 - gene b.
很遗憾,这个类的JavaDoc指出“它仅用于内部使用”,所以对于任何希望使用它的人来说,这可能是一个风险。 - spaceman spiff
1
@spacemanspiff 你在技术上是正确的,但这个类已经存在了大约15年(包括4个主要版本),并且已经被许多Spring客户广泛使用。我怀疑他们现在会撤回它。 - Sean Patrick Floyd

38
这将做到它:
private List<Field> getInheritedPrivateFields(Class<?> type) {
    List<Field> result = new ArrayList<Field>();

    Class<?> i = type;
    while (i != null && i != Object.class) {
        Collections.addAll(result, i.getDeclaredFields());
        i = i.getSuperclass();
    }

    return result;
}

如果你使用像EclEmma这样的代码覆盖率工具,你需要注意:它们会为每个类添加一个隐藏字段。在EclEmma的情况下,这些字段被标记为synthetic,你可以按照以下方式过滤它们:
private List<Field> getInheritedPrivateFields(Class<?> type) {
    List<Field> result = new ArrayList<Field>();

    Class<?> i = type;
    while (i != null && i != Object.class) {
        for (Field field : i.getDeclaredFields()) {
            if (!field.isSynthetic()) {
                result.add(field);
            }
        }
        i = i.getSuperclass();
    }

    return result;
}

感谢您对合成字段的评论,EMMA也是如此。 - Anatoliy
这将获取参数类的已声明和继承字段,因此应该命名为getDeclaredAndInheritedPrivateFields。非常完美,谢谢! - Peter Hawkins
1
isSynthetic 很棒的捕捉 :) - Lucas Crawford
非常感谢您的出色回答~ - VimNing

21
public static Field getField(Class<?> clazz, String fieldName) {
    Class<?> tmpClass = clazz;
    do {
        try {
            Field f = tmpClass.getDeclaredField(fieldName);
            return f;
        } catch (NoSuchFieldException e) {
            tmpClass = tmpClass.getSuperclass();
        }
    } while (tmpClass != null);

    throw new RuntimeException("Field '" + fieldName
            + "' not found on class " + clazz);
}
(根据这个答案)

17

事实上,我使用了一个复杂的类型层次结构,因此你的解决方案并不完整。 我需要进行递归调用以获取所有私有继承字段。 以下是我的解决方案

 /**
 * Return the set of fields declared at all level of class hierachy
 */
public static List<Field> getAllFields(Class<?> clazz) {
    return getAllFieldsRec(clazz, new ArrayList<>());
}

private static List<Field> getAllFieldsRec(Class<?> clazz, List<Field> list) {
    Class<?> superClazz = clazz.getSuperclass();
    if (superClazz != null) {
        getAllFieldsRec(superClazz, list);
    }
    list.addAll(Arrays.asList(clazz.getDeclaredFields()));
    return list;
}

然而,他的解决方案确实让你走上了正确的道路,不是吗? - aperkins
1
向量是过时的糟糕代码。请使用集合框架中的当前数据结构(在大多数情况下,ArrayList是足够的)。 - Sean Patrick Floyd
@aperkins,aioobe的答案看起来和我的一样,但我先找到了它,然后才看到答案。@seanizer Vector并不是很古老,并且它是集合API的成员。 - benzen
自Java 2平台v1.2起,该类已经进行了改装以实现List接口,因此它成为了Java集合框架的一部分。如果这还不算老,那还有什么是老的呢?来源:http://download.oracle.com/javase/1.4.2/docs/api/java/util/Vector.html - Sean Patrick Floyd
8
由于Vector会进行同步操作,因此存在巨大的开销。而且,在需要进行同步操作时,java.util.concurrent中有更好的类可供使用。在大多数情况下,应该用ArrayList、HashMap和StringBuilder来替换Vector、Hashtable和StringBuffer。 - Sean Patrick Floyd

8
private static Field getField(Class<?> clazz, String fieldName) {
    Class<?> tmpClass = clazz;
    do {
        for ( Field field : tmpClass.getDeclaredFields() ) {
            String candidateName = field.getName();
            if ( ! candidateName.equals(fieldName) ) {
                continue;
            }
            field.setAccessible(true);
            return field;
        }
        tmpClass = tmpClass.getSuperclass();
    } while ( clazz != null );
    throw new RuntimeException("Field '" + fieldName +
        "' not found on class " + clazz);
}

8

Model Citizen 中,我需要为蓝图添加继承字段的支持。我提出了这种方法,它更为简洁,可以检索类的字段和继承字段。

private List<Field> getAllFields(Class clazz) {
    List<Field> fields = new ArrayList<Field>();

    fields.addAll(Arrays.asList(clazz.getDeclaredFields()));

    Class superClazz = clazz.getSuperclass();
    if(superClazz != null){
        fields.addAll(getAllFields(superClazz));
    }

    return fields;
}

-1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接