Como ler o valor de um campo privado de uma classe diferente em Java?

482

Tenho uma classe mal projetada de terceiros JARe preciso acessar um de seus campos particulares . Por exemplo, por que devo escolher um campo privado é necessário?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

Como posso usar a reflexão para obter o valor de stuffIWant?

Frank Krueger
fonte

Respostas:

668

Para acessar campos particulares, você precisa obtê-los dos campos declarados da classe e torná-los acessíveis:

Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException

EDIT : como comentado por aperkins , acessando o campo, configurando-o como acessível e recuperando o valor pode gerar Exceptions, embora as únicas exceções verificadas das quais você precisa estar ciente sejam comentadas acima.

A NoSuchFieldExceptionseria lançada se você pediu um campo por um nome que não corresponde a um campo declarado.

obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException

A IllegalAccessExceptionseria lançada se o campo não era acessível (por exemplo, se é privado e não foi tornado acessível via perdendo a f.setAccessible(true)linha.

Os RuntimeExceptions que podem ser lançados são SecurityExceptions (se as JVMs SecurityManagernão permitirem alterar a acessibilidade de um campo) ou IllegalArgumentExceptions, se você tentar acessar o campo em um objeto que não seja do tipo da classe do campo:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
oxbow_lakes
fonte
4
você pode explicar os comentários de exceção? o código será executado, mas lançará exceções? ou o código pode gerar exceções?
Nir Levy
1
@Nir - Não - provavelmente o código funcionará bem (como o SecurityManager padrão permitirá que a acessibilidade dos campos seja alterada) - mas você precisa lidar com exceções verificadas (pegue-as ou declare-as novamente ). Eu modifiquei minha resposta um pouco. Pode ser bom para você escrever um caso de teste pequena para jogar ao redor e ver o que acontece
oxbow_lakes
3
Desculpe, esta resposta é confusa para mim. Talvez mostre um exemplo de manipulação de exceção típica. Parece que as exceções ocorreriam quando as classes fossem conectadas incorretamente. O exemplo de código faz parecer que as exceções seriam lançadas nas linhas correspondentes.
LaFayette 29/02
2
getDeclaredField()não encontra campos se eles estiverem definidos nas classes pai - você também deve percorrer a hierarquia da classe pai, chamando getDeclaredField()cada um, até encontrar uma correspondência (após a qual você pode chamar setAccessible(true)) ou alcançar Object.
9177 Luke Hutchison
1
@ legend, você pode instalar um gerenciador de segurança para proibir esse acesso. Desde o Java 9, esse acesso não deve funcionar além das fronteiras do módulo (a menos que um módulo seja aberto explicitamente), embora haja uma fase de transição em relação à imposição das novas regras. Além disso, exigir esforços adicionais como Reflexão sempre fazia diferença para os não- privatecampos. Isso impede o acesso acidental.
Holger
159

Tente FieldUtilsdo apache commons-lang3:

FieldUtils.readField(object, fieldName, true);
yegor256
fonte
76
Estou convencido de que você poderia resolver a maioria dos problemas do mundo reunindo alguns métodos de commons-lang3.
Cameron
@ yegor porque java permite isso? significa por que o java permite acessar membros privados?
Asif Mushtaq
1
@ yegor256 Ainda consigo acessar membros privados em C # e C ++ .. !! Então? todos os criadores de idiomas são fracos?
Asif Mushtaq
3
@UnKnown java não. Há duas coisas diferentes - linguagem java e java como máquina virtual java. Este último opera em bytecode. E existem bibliotecas para manipular isso. Portanto, a linguagem java não permite que você use campos particulares fora do escopo ou não modifique as referências finais. Mas é possível fazer isso usando a ferramenta. Que por acaso está dentro da biblioteca padrão.
evgenii
3
@Uma das razões é que o Java não tem acesso de "amigo" para permitir o acesso ao campo apenas por uma estrutura de infraestrutura como o serializador DI, ORM ou XML / JSON. Essas estruturas precisam acessar os campos do objeto para serializar ou inicializar corretamente o estado interno de um objeto, mas você ainda pode precisar da aplicação adequada do encapsulamento em tempo de compilação para sua lógica de negócios.
precisa
26

A reflexão não é a única maneira de resolver seu problema (que é acessar a funcionalidade / comportamento privado de uma classe / componente)

Uma solução alternativa é extrair a classe do .jar, descompilar usando (digamos) Jode ou Jad , alterar o campo (ou adicionar um acessador) e recompilar no arquivo .jar original. Em seguida, coloque o novo .class à frente do .jarno caminho de classe ou reinsira-o no .jar. (o utilitário jar permite extrair e reinserir em um arquivo .jar existente)

Conforme observado abaixo, isso resolve a questão mais ampla de acessar / alterar o estado privado, em vez de simplesmente acessar / alterar um campo.

Isso requer que .jarnão seja assinado, é claro.

Brian Agnew
fonte
36
Dessa forma, será bastante doloroso para apenas um campo simples.
Valentin Rocher
1
Discordo. Ele permite que você não apenas acesse seu campo, mas também altere a classe, se necessário, se o acesso ao campo não for suficiente.
27909 Brian Agnew
4
Então você tem que fazer isso de novo. O que acontece se o jar obtiver uma atualização e você usar a reflexão para um campo que não existe mais? É exatamente o mesmo problema. Você apenas tem que gerenciar isso.
precisa
4
Estou espantado a respeito de quanto isso fica downvoted dada a) é destacada como uma prática alternativa b) que atende a cenários em que mudam a visibilidade de um campo não é suficiente
Brian Agnew
2
@BrianAgnew talvez seja apenas semântico, mas se mantivermos a questão (usar reflexão para ler um campo privado) não usar reflexão é imediatamente uma autocontradição. Mas eu concordo que você forneça acesso ao campo ... mas ainda assim o campo não é mais privado. Mais uma vez, não nos ateremos ao "ler um campo privado" da pergunta. De outro ângulo, a modificação .jar pode não funcionar em alguns casos (jar assinado), precisa ser feita toda vez que o jar é atualizado, requer manipulação cuidadosa do caminho de classe (que você pode não controlar totalmente se for executado em um recipiente de aplicação), etc
Remi Morin
18

Uma outra opção que ainda não foi mencionada: use o Groovy . O Groovy permite acessar variáveis ​​de instância privadas como efeito colateral do design do idioma. Independentemente de você ter um getter para o campo, basta usar

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
lucas
fonte
4
O OP solicitou especificamente Java
Jochen
3
Muitos projetos Java hoje incorporam groovy. Basta que o projeto use o DSL groovy do Spring e eles tenham groovy no caminho de classe, por exemplo. Nesse caso, essa resposta é útil e, embora não atenda diretamente ao OP, será benéfica para muitos visitantes.
Amir Abiri
10

Usando o Reflection in Java, você pode acessar todos os private/publiccampos e métodos de uma classe para outra. Mas, conforme a documentação do Oracle nas desvantagens da seção, eles recomendaram que:

"Como a reflexão permite que o código execute operações que seriam ilegais no código não-reflexivo, como acessar campos e métodos privados, o uso da reflexão pode resultar em efeitos colaterais inesperados, que podem tornar o código disfuncional e destruir a portabilidade. Código reflexivo quebra abstrações e, portanto, pode alterar o comportamento com atualizações da plataforma "

aqui está o código snapts para demonstrar os conceitos básicos de reflexão

Reflection1.java

public class Reflection1{

    private int i = 10;

    public void methoda()
    {

        System.out.println("method1");
    }
    public void methodb()
    {

        System.out.println("method2");
    }
    public void methodc()
    {

        System.out.println("method3");
    }

}

Reflection2.java

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class Reflection2{

    public static void main(String ar[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        Method[] mthd = Reflection1.class.getMethods(); // for axis the methods 

        Field[] fld = Reflection1.class.getDeclaredFields();  // for axis the fields  

        // Loop for get all the methods in class
        for(Method mthd1:mthd)
        {

            System.out.println("method :"+mthd1.getName());
            System.out.println("parametes :"+mthd1.getReturnType());
        }

        // Loop for get all the Field in class
        for(Field fld1:fld)
        {
            fld1.setAccessible(true);
            System.out.println("field :"+fld1.getName());
            System.out.println("type :"+fld1.getType());
            System.out.println("value :"+fld1.getInt(new Reflaction1()));
        }
    }

}

Espero que ajude.

Simmant
fonte
5

Como oxbow_lakes menciona, você pode usar a reflexão para contornar as restrições de acesso (supondo que o SecurityManager permita).

Dito isto, se essa classe é tão mal projetada que faz com que você recorra a tal hackery, talvez você deva procurar uma alternativa. Claro que esse pequeno truque pode economizar algumas horas agora, mas quanto vai custar no futuro?

Laurence Gonsalves
fonte
3
Sou mais sortudo do que isso, na verdade, só estou usando esse código para extrair alguns dados, depois posso jogá-lo de volta na Lixeira.
31430 Frank Krueger
2
Bem, nesse caso, corte fora. :-)
Laurence Gonsalves
3
Isso não fornece uma resposta para a pergunta. Para criticar ou solicitar esclarecimentos a um autor, deixe um comentário abaixo da postagem.
Mureinik
@Mureinik - Responde à pergunta, com as palavras "você pode usar a reflexão". Falta um exemplo ou qualquer explicação maior ou como, mas é uma resposta. Voto negativo se você não gostar.
ArtOfWarfare
4

Use a estrutura Soot Java Optimization para modificar diretamente o bytecode. http://www.sable.mcgill.ca/soot/

A fuligem é completamente escrita em Java e funciona com novas versões do Java.

pcpratts
fonte
3

Você precisa fazer o seguinte:

private static Field getField(Class<?> cls, String fieldName) {
    for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
        try {
            final Field field = c.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        } catch (final NoSuchFieldException e) {
            // Try parent
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "Cannot access field " + cls.getName() + "." + fieldName, e);
        }
    }
    throw new IllegalArgumentException(
            "Cannot find field " + cls.getName() + "." + fieldName);
}
Luke Hutchison
fonte
2

Se você estiver usando o Spring, o ReflectionTestUtils fornece algumas ferramentas úteis que ajudam aqui com o mínimo esforço. É descrito como "para uso em cenários de teste de unidade e integração" . Há também uma classe semelhante chamada ReflectionUtils, mas é descrita como "Somente para uso interno" - veja esta resposta para uma interpretação do que isso significa.

Para abordar o exemplo postado:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
Steve Chambers
fonte
1
Se você decidir usar uma classe Utils até o Spring, você realmente deve usar a classe sem teste ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ), a menos que, é claro, você na verdade, estamos usando-o para testes de unidade.
Sync
Possivelmente, embora essa classe seja descrita como "Somente para uso interno". Adicionei algumas informações à resposta sobre isso. (Mantive o exemplo usando ReflectionTestUtilscomo na minha experiência, eu só precisei fazer esse tipo de coisa em uma situação de teste.) #
933 Steve Chambers Steve
1

Apenas uma observação adicional sobre reflexão: observei em alguns casos especiais, quando várias classes com o mesmo nome existem em pacotes diferentes, que a reflexão usada na resposta superior pode não conseguir escolher a classe correta do objeto. Portanto, se você souber qual é o package.class do objeto, é melhor acessar seus valores de campo privado da seguinte maneira:

org.deeplearning4j.nn.layers.BaseOutputLayer ll = (org.deeplearning4j.nn.layers.BaseOutputLayer) model.getLayer(0);
Field f = Class.forName("org.deeplearning4j.nn.layers.BaseOutputLayer").getDeclaredField("solver");
f.setAccessible(true);
Solver s = (Solver) f.get(ll);

(Esta é a classe de exemplo que não estava funcionando para mim)

xtof54
fonte
1

Você pode usar o @JailBreak do Manifold para refletir Java diretamente e com segurança de tipo:

@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;

public class Foo {
    private String stuffIWant;
}

@JailBreakdesbloqueia a foovariável local no compilador para acesso direto a todos os membros na Foohierarquia.

Da mesma forma, você pode usar o método de extensão jailbreak () para uso único:

foo.jailbreak().stuffIWant = "123";

Através do jailbreak()método, você pode acessar qualquer membro da Foohierarquia.

Nos dois casos, o compilador resolve o acesso ao campo para você digitar com segurança, como se fosse um campo público, enquanto o Manifold gera um código de reflexão eficiente para você.

Descubra mais sobre o coletor .

Scott
fonte
1

É bastante fácil com a ferramenta XrayInterface . Basta definir os getters / setters ausentes, por exemplo

interface BetterDesigned {
  Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}

e xray seu projeto mal projetado:

IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());

Internamente, isso depende de reflexão.

CoronA
fonte
0

Tente solucionar o problema do caso, cuja classe você deseja definir / obter dados é uma de suas próprias classes.

Basta criar um public setter(Field f, Object value)e public Object getter(Field f)para isso. Você pode até fazer alguma verificação de segurança por conta própria dentro dessas funções de membro. Por exemplo, para o levantador:

class myClassName {
    private String aString;

    public set(Field field, Object value) {
        // (A) do some checkings here  for security

        // (B) set the value
        field.set(this, value);
    }
}

Claro, agora você tem que descobrir o java.lang.reflect.Fieldpara sStringantes de definir o valor campo.

Eu uso essa técnica em um ResultSet genérico para o mapeador de modelo.

karldegen
fonte