Zugriff auf private geerbte Felder über Reflection in Java

109

Ich habe einen Weg gefunden, geerbte Mitglieder über zu erhalten class.getDeclaredFields(); und auf private Mitglieder über zuzugreifen. class.getFields() Aber ich suche nach privaten geerbten Feldern. Wie kann ich das erreichen?

Benzol
quelle
28
"Private geerbte Felder" existiert nicht. Wenn ein Feld privat ist, wird es nicht vererbt und bleibt nur im Bereich der übergeordneten Klasse. Um auf übergeordnete private Felder zuzugreifen, müssen Sie zuerst auf die übergeordnete Klasse zugreifen (vgl. Aioobes Antwort)
Benoit Courtine
6
Das heißt, geschützte Felder werden vererbt, aber Sie müssen dasselbe tun, um sie durch Reflexion zu erhalten.
Bozho

Antworten:

128

Dies sollte zeigen, wie es gelöst werden kann:

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

(Oder Class.getDeclaredFieldsfür ein Array aller Felder.)

Ausgabe:

5
aioobe
quelle
Erhält dies alle Felder der Oberklasse oder nur die direkte Oberklasse?
Regen
Direkte Superklassenfelder. Sie können weiterarbeiten, getSuperclass()bis Sie erreichen, nullwenn Sie höher gehen möchten.
Aioobe
Warum verwenden getDeclaredFields()[0]oder getDeclaredField("i")wiederholen Sie den [0]Array-Zugriff nicht in den nächsten beiden Anweisungen?
Holger
Es liegt an der Art und Weise, wie diese spezielle Frage formuliert ist. Es war im Grunde nur eine Demonstration der Verwendung getDeclaredFields. Die Antwort wurde aktualisiert.
Aioobe
44

Der beste Ansatz hier ist die Verwendung des Besuchermusters. Finden Sie alle Felder in der Klasse und alle Superklassen und führen Sie eine Rückrufaktion für sie aus.


Implementierung

Spring hat eine nette Utility-Klasse ReflectionUtils, die genau das tut: Sie definiert eine Methode, um alle Felder aller Superklassen mit einem Rückruf zu durchlaufen:ReflectionUtils.doWithFields()

Dokumentation:

Rufen Sie den angegebenen Rückruf für alle Felder in der Zielklasse auf und gehen Sie die Klassenhierarchie nach oben, um alle deklarierten Felder abzurufen.

Parameter:
- clazz - die zu analysierende Zielklasse -
fc - der für jedes Feld aufzurufende Rückruf
- ff - der Filter, der die Felder bestimmt, auf die der Rückruf angewendet werden soll

Beispielcode:

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

Ausgabe:

Gefunden Feld Private transient boolean javax.management.relation.RoleUnresolvedList.typeSafe in Typklasse javax.management.relation.RoleUnresolvedList
gefunden Feld Private transiente boolean javax.management.relation.RoleUnresolvedList.tainted in Typklasse javax.management.relation.RoleUnresolvedList
gefunden Feld private transient java.lang.Object [] java.util.ArrayList.elementData in der Typklasse java.util.ArrayList
Gefundenes Feld private int java.util.ArrayList.size in der Typklasse java.util.ArrayList
Gefundenes Feld geschützt transient int java. util.AbstractList.modCount in der Typklasse java.util.AbstractList

Sean Patrick Floyd
quelle
3
Das ist kein "Besuchermuster", aber es ist immer noch ein sehr schönes Werkzeug, wenn Sie den Spring-Virus in Ihrem Code haben. danke fürs teilen :)
thinlizzy
2
@ jose.diego Ich bin mir ziemlich sicher, dass Sie darüber streiten könnten. Es besucht eher eine Klassenhierarchie als einen Objektbaum, aber das Prinzip bleibt dasselbe
Sean Patrick Floyd
Sie sind sich nicht sicher, ob dieser Kommentar eine Antwort erhält, aber mit dieser Lösung besuchen Sie jeweils nur ein bestimmtes Feld. Wenn ich gleichzeitig andere Felder betrachten muss - z. B. dieses Feld auf "abc" setzen, wenn ein anderes Feld NULL ist -, steht mir das gesamte Objekt nicht zur Verfügung.
Gen b.
Es ist schade, dass das JavaDoc für diese Klasse angibt, dass "es nur für den internen Gebrauch bestimmt ist", daher ist dies ein mögliches Risiko für jeden, der es verwenden möchte.
Raumfahrer Spiff
1
@spacemanspiff Sie sind technisch korrekt, aber diese Klasse gibt es seit ungefähr 15 Jahren (einschließlich 4 Hauptversionen) und wurde von vielen Spring-Kunden häufig verwendet. Ich bezweifle, dass sie es jetzt zurückziehen werden.
Sean Patrick Floyd
34

Das wird es tun:

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

Wenn Sie ein Tool zur Codeabdeckung wie EclEmma verwenden , müssen Sie darauf achten , dass jeder Ihrer Klassen ein verstecktes Feld hinzugefügt wird. Im Fall von EclEmma sind diese Felder als synthetisch markiert , und Sie können sie wie folgt herausfiltern:

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
quelle
Vielen Dank für Ihre Bemerkung zu synthetischen Feldern. EMMA macht dasselbe.
Anatoliy
Dies wird deklariert und geerbt Felder der Argumentklasse, sollte also getDeclaredAndInheritedPrivateFields heißen. perfekt aber danke!
Peter Hawkins
1
schöner Fang auf der isSynthetic :)
Lucas Crawford
Vielen Dank für die hervorragende Antwort ~
Regen
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);
}

(basierend auf dieser Antwort)

Exterminator13
quelle
15

Tatsächlich verwende ich eine komplexe Hierarchie, so dass Ihre Lösung nicht vollständig ist. Ich muss einen rekursiven Aufruf durchführen, um alle privaten geerbten Felder abzurufen. Hier ist meine Lösung

 /**
 * 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;
}
Benzol
quelle
Seine Lösung hat Sie jedoch auf den richtigen Weg gebracht, nicht wahr?
Aperkins
1
Vektor ist schlechter alter Code. Bitte verwenden Sie eine aktuelle Datenstruktur aus dem Sammlungsframework (ArrayList ist in den meisten Fällen ausreichend)
Sean Patrick Floyd
@aperkins die Antwort von aioobe sieht aus wie meine, aber ich habe sie gefunden und dann habe ich die Antwort gesehen. @ Seanizer Vector ist nicht so alt, und es ist ein Mitglied der Sammlung API
Benzol
"Ab der Java 2-Plattform v1.2 wurde diese Klasse für die Implementierung von List nachgerüstet, sodass sie Teil des Java-Sammlungsframeworks wird." in 1.2 nachgerüstet? Wenn das nicht alt ist, was dann? Quelle: download.oracle.com/javase/1.4.2/docs/api/java/util/Vector.html
Sean Patrick Floyd
7
Vektor hat einen enormen Overhead, weil alles synchronisiert ist. Und wo Sie eine Synchronisation benötigen, gibt es in java.util.concurrent bessere Klassen. Vector, Hashtable und StringBuffer sollten in den meisten Fällen durch ArrayList, HashMap und StringBuilder ersetzt werden
Sean Patrick Floyd
8

Ich musste Unterstützung für geerbte Felder für Blaupausen in Model Citizen hinzufügen . Ich habe diese Methode abgeleitet, die etwas präziser ist, um Felder + geerbte Felder einer Klasse abzurufen.

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
quelle
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
quelle