Acesso a campos herdados privados via reflexão em Java

109

Eu encontrei uma maneira de obter membros herdados via class.getDeclaredFields(); e acesso a membros privados via class.getFields() Mas estou procurando por campos herdados privados. Como posso conseguir isso?

Benzen
fonte
28
"campos herdados privados" não existe. Se um campo for privado, ele não será herdado e permanecerá apenas no escopo da classe pai. Para acessar os campos privados principais, você deve acessar a classe principal primeiro (cf. a resposta de aioobe)
Benoit Courtine
6
Dito isso, os campos protegidos são herdados, mas você precisa fazer o mesmo para obtê-los por reflexão.
Bozho

Respostas:

128

Isso deve demonstrar como resolvê-lo:

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.getDeclaredFieldspara uma matriz de todos os campos.)

Resultado:

5
aioobe
fonte
Isso obtém todos os campos das superclasses ou apenas a superclasse direta?
Chovendo
Campos de superclasses diretos. Você pode recursar getSuperclass()até chegar nullse quiser ir mais alto.
aioobe
Por que você não usa getDeclaredFields()[0]ou, em getDeclaredField("i")vez disso, repete o [0]acesso à matriz nas próximas duas instruções?
Holger
É devido à forma como essa questão em particular é formulada. Foi basicamente uma demonstração de como usar getDeclaredFields. A resposta foi atualizada.
aioobe
44

A melhor abordagem aqui é usar o padrão de visitante para localizar todos os campos na classe e todas as superclasses e executar uma ação de retorno de chamada neles.


Implementação

Spring tem uma boa classe Utility ReflectionUtilsque faz exatamente isso: define um método para fazer um loop em todos os campos de todas as superclasses com um retorno de chamada:ReflectionUtils.doWithFields()

Documentação:

Invoque o retorno de chamada fornecido em todos os campos da classe de destino, subindo na hierarquia de classes para obter todos os campos declarados.

Parâmetros:
- clazz - a classe de destino a ser analisada
- fc - o callback a ser invocado para cada campo
- ff - o filtro que determina os campos aos quais aplicar o callback

Código de amostra:

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

Resultado:

Encontrado campo private transient boolean javax.management.relation.RoleUnresolvedList.typeSafe na classe de tipo javax.management.relation.RoleUnresolvedList
Campo encontrado private transient boolean javax.management.relation.RoleUnresolvedList.tainted na classe de tipo javax.managementUnresolvedList
. private transient java.lang.Object [] java.util.ArrayList.elementData no tipo classe java.util.ArrayList
Campo encontrado private int java.util.ArrayList.size no tipo classe java.util.ArrayList
Campo encontrado protegido transiente int java. util.AbstractList.modCount na classe de tipo java.util.AbstractList

Sean Patrick Floyd
fonte
3
isso não é um "padrão de visitante", mas ainda é uma ferramenta muito boa se você tiver o vírus Spring em seu código. obrigado por compartilhar :)
thinlizzy
2
@ jose.diego Tenho certeza de que você poderia argumentar sobre isso. Ele visita uma hierarquia de classes em vez de uma árvore de objetos, mas o princípio permanece o mesmo
Sean Patrick Floyd
Não tenho certeza se este comentário receberá uma resposta, mas você está visitando apenas um campo específico por vez com esta solução. Se eu precisar examinar outros campos ao mesmo tempo - por exemplo, definir este campo como "abc" se outro campo for NULL - não tenho o objeto como um todo disponível para mim.
gene b.
É uma pena que o JavaDoc para esta classe indique que "ele é destinado apenas para uso interno", portanto, é um risco possível para quem deseja usá-lo.
astronauta spiff
1
@spacemanspiff, você está tecnicamente correto, mas esta classe existe há cerca de 15 anos (incluindo 4 versões principais) e tem sido amplamente usada por muitos clientes do Spring. Duvido que eles puxem de volta agora.
Sean Patrick Floyd
34

Isso vai resolver:

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

Se você usa uma ferramenta de cobertura de código como o EclEmma , deve ficar atento: eles adicionam um campo oculto a cada uma de suas classes. No caso do EclEmma, ​​esses campos são marcados como sintéticos e você pode filtrá-los assim:

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
fonte
Obrigado por sua observação sobre campos sintéticos, EMMA faz o mesmo.
Anatoliy
isso obtém campos declarados e herdados da classe de argumento, portanto, deve ser nomeado getDeclaredAndInheritedPrivateFields. perfeito embora, obrigado!
Peter Hawkins
1
boa captura no isSynthetic :)
Lucas Crawford
Obrigado pela resposta excelente ~
Chovendo
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);
}

(com base nesta resposta)

Exterminator 13
fonte
15

Na verdade, eu uso uma hierarquia de tipo complexa, então sua solução não está completa. Preciso fazer uma chamada recursiva para obter todos os campos herdados privados. Aqui está minha solução

 /**
 * 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
fonte
No entanto, a solução dele o colocou no caminho certo, não foi?
aperkins de
1
O vetor é um código antigo ruim. Use uma estrutura de dados atual do framework de coleções (ArrayList é adequado na maioria dos casos)
Sean Patrick Floyd
@aperkins a resposta de aioobe parece com a minha, mas eu encontrei e então vi a resposta. @seanizer Vector não é tão antigo e é um membro da API de coleção
benzen
"A partir da plataforma Java 2 v1.2, esta classe foi adaptada para implementar List, para que se torne parte da estrutura de coleção do Java." adaptado em 1.2? se não é antigo, o que é? Fonte: download.oracle.com/javase/1.4.2/docs/api/java/util/Vector.html
Sean Patrick Floyd
7
O vetor tem uma grande sobrecarga porque tudo está sincronizado. E onde você precisa de sincronização, existem classes melhores em java.util.concurrent. Vector, Hashtable e StringBuffer devem, na maioria dos casos, ser substituídos por ArrayList, HashMap e StringBuilder
Sean Patrick Floyd
8

Eu precisava adicionar suporte para campos herdados para blueprints no Model Citizen . Eu deduzi esse método que é um pouco mais conciso para recuperar os campos + campos herdados de uma classe.

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