Acceso a campos heredados privados a través de la reflexión en Java

109

Encontré una manera de obtener miembros heredados class.getDeclaredFields(); y acceso a miembros privados a través de class.getFields() Pero estoy buscando campos heredados privados. ¿Cómo puedo conseguir esto?

benzen
fuente
28
"campos privados heredados" no existe. Si un campo es privado, no se hereda y permanece solo en el ámbito de la clase principal. Para acceder a los campos privados para padres, primero debe acceder a la clase para padres (consulte la respuesta de aioobe)
Benoit Courtine
6
Dicho esto, los campos protegidos se heredan, pero debe hacer lo mismo para obtenerlos por reflexión.
Bozho

Respuestas:

128

Esto debería demostrar cómo resolverlo:

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

(O Class.getDeclaredFieldspara una matriz de todos los campos).

Salida:

5
aioobe
fuente
¿Obtiene esto todos los campos de las superclases o solo la superclase directa?
Lloviendo
Campos de superclases directas. Puede recurrir getSuperclass()hasta llegar a nullsi desea ir más alto.
aioobe
¿Por qué no usa getDeclaredFields()[0]o getDeclaredField("i")repite el [0]acceso a la matriz en las siguientes dos declaraciones?
Holger
Se debe a la forma en que se formula esta pregunta en particular. Básicamente fue solo una demostración de cómo usarlo getDeclaredFields. La respuesta se ha actualizado.
aioobe
44

El mejor enfoque aquí es usar el Patrón de visitante para encontrar todos los campos de la clase y todas las superclases y ejecutar una acción de devolución de llamada en ellos.


Implementación

Spring tiene una buena clase de utilidad ReflectionUtilsque hace precisamente eso: define un método para recorrer todos los campos de todas las superclases con una devolución de llamada:ReflectionUtils.doWithFields()

Documentación:

Invoque la devolución de llamada dada en todos los campos de la clase de destino, subiendo por la jerarquía de clases para obtener todos los campos declarados.

Parámetros:
- clazz - la clase de destino a analizar
- fc - la devolución de llamada a invocar para cada campo
- ff - el filtro que determina los campos a los que aplicar la devolución de llamada

Código de muestra:

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

Salida:

Encontrado campo booleano transitoria privada javax.management.relation.RoleUnresolvedList.typeSafe en la clase de tipo javax.management.relation.RoleUnresolvedList
encontrado ámbito privado transitoria booleano javax.management.relation.RoleUnresolvedList.tainted en javax.management.relation.RoleUnresolvedList clase de tipo de
terreno encontrado private transient java.lang.Object [] java.util.ArrayList.elementData en la clase de tipo java.util.ArrayList
Campo encontrado private int java.util.ArrayList.size en la clase de tipo java.util.ArrayList
Campo encontrado protegido transitorio int java. util.AbstractList.modCount en la clase de tipo java.util.AbstractList

Sean Patrick Floyd
fuente
3
eso no es un "patrón de visitante", pero sigue siendo una herramienta muy buena si tiene el virus Spring en su código. gracias por compartirlo :)
thinlizzy
2
@ jose.diego Estoy bastante seguro de que podrías discutir sobre eso. Visita una jerarquía de clases en lugar de un árbol de objetos, pero el principio sigue siendo el mismo
Sean Patrick Floyd
No estoy seguro de si este comentario obtendrá una respuesta, pero solo está visitando un campo en particular a la vez con esta solución. Si necesito mirar otros campos al mismo tiempo, por ejemplo, establezca este campo en "abc" si otro campo es NULL, no tengo el objeto completo disponible para mí.
gen b.
Es una lástima que el JavaDoc para esta clase indique que "solo está diseñado para uso interno", por lo que es un posible riesgo para cualquiera que desee utilizarlo.
astronauta spiff
1
@spacemanspiff, es técnicamente correcto, pero esta clase ha existido durante unos 15 años (incluidas 4 versiones de lanzamiento principales) y ha sido ampliamente utilizada por muchos clientes de Spring. Dudo que lo retiren ahora.
Sean Patrick Floyd
34

Esto lo hará:

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 usa una herramienta de cobertura de código como EclEmma , debe tener cuidado: agregan un campo oculto a cada una de sus clases. En el caso de EclEmma, ​​estos campos están marcados como sintéticos y puede filtrarlos así:

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
fuente
Gracias por su comentario sobre los campos sintéticos, EMMA hace lo mismo.
Anatoliy
esto obtiene campos declarados y heredados de la clase de argumento, por lo que debe llamarse getDeclaredAndInheritedPrivateFields. perfecto aunque gracias!
Peter Hawkins
1
buena captura en el isSynthetic :)
Lucas Crawford
Gracias por la excelente respuesta ~
Lloviendo
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);
}

(basado en esta respuesta)

Exterminador13
fuente
15

De hecho, utilizo una jerarquía de tipos complejos, por lo que su solución no está completa. Necesito hacer una llamada recursiva para obtener todos los campos heredados privados. Aqui esta mi solucion

 /**
 * 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
fuente
Sin embargo, su solución te puso en el camino correcto, ¿no?
Aperkins
1
El vector es un código antiguo malo. Utilice una estructura de datos actual del marco de colecciones (ArrayList es adecuado en la mayoría de los casos)
Sean Patrick Floyd
@aperkins la respuesta de aioobe se parece a la mía, pero la encontré y luego vi la respuesta. @seanizer Vector no es tan viejo, y es un miembro de la API de la colección
benzen
"A partir de la plataforma Java 2 v1.2, esta clase se ha actualizado para implementar List, de modo que se convierta en parte del marco de la colección de Java". reequipado en 1.2? si eso no es viejo entonces que es? Fuente: download.oracle.com/javase/1.4.2/docs/api/java/util/Vector.html
Sean Patrick Floyd
7
Vector tiene una sobrecarga enorme porque todo está sincronizado. Y donde necesite sincronización, hay mejores clases en java.util.concurrent. Vector, Hashtable y StringBuffer deberían ser reemplazados en la mayoría de los casos por ArrayList, HashMap y StringBuilder
Sean Patrick Floyd
8

Necesitaba agregar soporte para campos heredados para planos en Model Citizen . Derivé este método que es un poco más conciso para recuperar los campos de una clase + campos heredados.

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