Accès aux champs hérités privés via la réflexion en Java

109

J'ai trouvé un moyen d'obtenir des membres hérités via class.getDeclaredFields(); et d'accéder aux membres privés via class.getFields() Mais je recherche des champs hérités privés. Comment puis-je atteindre cet objectif?

benzen
la source
28
"champs hérités privés" n'existe pas. Si un champ est privé, il n'est pas hérité et reste uniquement dans la portée de la classe parent. Pour accéder aux champs privés parents, vous devez d'abord accéder à la classe parent (cf. réponse d'Aioobe)
Benoit Courtine
6
cela dit, les champs protégés sont hérités, mais il faut faire de même pour les obtenir par réflexion.
Bozho

Réponses:

128

Cela devrait montrer comment le résoudre:

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

(Ou Class.getDeclaredFieldspour un tableau de tous les champs.)

Production:

5
aioobe
la source
Est-ce que cela obtient tous les champs des superclasses ou juste la superclasse directe?
pleut
Champs directs des super classes. Vous pouvez continuer getSuperclass()jusqu'à ce que vous atteigniez nullsi vous voulez aller plus haut.
aioobe
Pourquoi n'utilisez-vous pas getDeclaredFields()[0]ou ne getDeclaredField("i")répétez-vous pas l' [0]accès au tableau dans les deux instructions suivantes?
Holger
Cela est dû à la façon dont cette question particulière est formulée. C'était essentiellement juste une démonstration de la façon d'utiliser getDeclaredFields. La réponse a été mise à jour.
aioobe
44

La meilleure approche ici consiste à utiliser le modèle de visiteur pour trouver tous les champs de la classe et toutes les super classes et exécuter une action de rappel sur eux.


la mise en oeuvre

Spring a une belle classe Utility ReflectionUtilsqui fait exactement cela: il définit une méthode pour boucler sur tous les champs de toutes les super classes avec un rappel:ReflectionUtils.doWithFields()

Documentation:

Appelez le rappel donné sur tous les champs de la classe cible, en remontant la hiérarchie des classes pour obtenir tous les champs déclarés.

Paramètres:
- clazz - la classe cible à analyser
- fc - le callback à invoquer pour chaque champ
- ff - le filtre qui détermine les champs auxquels appliquer le callback

Exemple de code:

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

Production:

Champ privé transitoire booléen javax.management.relation.RoleUnresolvedList.typeSafe
trouvé dans la classe de type javax.management.relation.RoleUnresolvedList Champ trouvé privé transitoire booléen javax.management.relation.RoleUnresolvedList.tainted dans la classe de type javax.management.relationList.Role
champ Foundresolved private transient java.lang.Object [] java.util.ArrayList.elementData dans la classe de type java.util.ArrayList
Champ trouvé private int java.util.ArrayList.size dans la classe de type java.util.ArrayList
Trouvé champ protected transient int java. util.AbstractList.modCount dans la classe de type java.util.AbstractList

Sean Patrick Floyd
la source
3
ce n'est pas un «modèle de visiteur», mais c'est toujours un très bon outil si vous avez le virus Spring dans votre code. merci de l'avoir partagé :)
thinlizzy
2
@ jose.diego Je suis presque sûr que vous pourriez en discuter. Il visite une hiérarchie de classes plutôt qu'un arbre d'objets, mais le principe reste le même
Sean Patrick Floyd
Je ne sais pas si ce commentaire obtiendra une réponse, mais vous ne visitez qu'un champ particulier à la fois avec cette solution. Si j'ai besoin de regarder d'autres champs en même temps - par exemple, définissez ce champ sur "abc" si un autre champ est NULL - je n'ai pas l'objet dans son ensemble à ma disposition.
gène b.
C'est dommage que le JavaDoc de cette classe indique qu '"il est uniquement destiné à un usage interne", c'est donc un risque possible pour quiconque souhaite l'utiliser.
spaceman spiff
1
@spacemanspiff vous avez techniquement raison, mais cette classe existe depuis environ 15 ans (y compris 4 versions majeures) et a été largement utilisée par de nombreux clients Spring. Je doute qu'ils le retirent maintenant.
Sean Patrick Floyd
34

Cela va le faire:

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

Si vous utilisez un outil de couverture de code comme EclEmma , vous devez faire attention: ils ajoutent un champ caché à chacune de vos classes. Dans le cas d'EclEmma, ​​ces champs sont marqués comme synthétiques et vous pouvez les filtrer comme ceci:

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;
}
jqno
la source
Merci pour votre remarque sur les champs synthétiques, EMMA fait de même.
Anatoliy
cela obtient les champs déclarés et hérités de la classe d'arguments et doit donc être nommé getDeclaredAndInheritedPrivateFields. parfait cependant merci!
Peter Hawkins
1
belle prise sur l'isSynthetic :)
Lucas Crawford
Merci pour la réponse superbe ~
pleut
19
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);
}

(basé sur cette réponse)

Exterminateur13
la source
15

En fait, j'utilise une hiérarchie de type complexe afin que votre solution ne soit pas complète. J'ai besoin de faire un appel récursif pour obtenir tous les champs hérités privés. Voici ma solution

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

private Vector<Field> getAllFieldsRec(Class clazz, Vector<Field> vector) {
    Class superClazz = clazz.getSuperclass();
    if(superClazz != null){
        getAllFieldsRec(superClazz, vector);
    }
    vector.addAll(toVector(clazz.getDeclaredFields()));
    return vector;
}
benzen
la source
Cependant, sa solution vous a mis sur la bonne voie, n'est-ce pas?
aperkins
1
Le vecteur est un mauvais vieux code. Veuillez utiliser une structure de données actuelle du cadre des collections (ArrayList est adéquate dans la plupart des cas)
Sean Patrick Floyd
@aperkins la réponse d'aioobe ressemble à la mienne, mais je l'ai trouvée et j'ai vu la réponse. @seanizer Vector n'est pas si vieux que ça, et c'est un membre de l'API de collection
benzen
"Depuis la plate-forme Java 2 v1.2, cette classe a été modernisée pour implémenter List, de sorte qu'elle devienne une partie du framework de collecte de Java." modernisé en 1.2? si ce n'est pas vieux alors qu'est-ce que c'est? Source: download.oracle.com/javase/1.4.2/docs/api/java/util/Vector.html
Sean Patrick Floyd
7
Vector a une surcharge énorme car tout est synchronisé. Et là où vous avez besoin de synchronisation, il existe de meilleures classes dans java.util.concurrent. Vector, Hashtable et StringBuffer devraient dans la plupart des cas être remplacés par ArrayList, HashMap et StringBuilder
Sean Patrick Floyd
8

J'avais besoin d'ajouter la prise en charge des champs hérités pour les plans dans Model Citizen . J'ai dérivé cette méthode qui est un peu plus concise pour récupérer les champs d'une classe + les champs hérités.

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;
}
mguymon
la source
7
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);
}
Kenny Cason
la source