Por que devo usar a reflexão?


29

Eu sou novo em Java; através dos meus estudos, li que a reflexão é usada para invocar classes e métodos e para saber quais métodos são implementados ou não.

Quando devo usar a reflexão e qual a diferença entre usar objetos de reflexão e instanciar e chamar métodos da maneira tradicional?



10
Faça sua parte da pesquisa antes de postar. Há muito material no StackExchange (como @Jalayn observou) e na web em geral sobre reflexão. Sugiro que você leia, por exemplo, o Java Tutorial on Reflection e volte depois, se tiver mais perguntas concretas.
Péter Török

11
Tem que haver um milhão de idiotas.
DeadMG

3
Mais do que alguns programadores profissionais responderiam "o mais raramente possível, talvez até nunca".
Ross Patterson

Respostas:


38
  • A reflexão é muito mais lenta do que chamar métodos pelo nome, porque é necessário inspecionar os metadados no código de bytes, em vez de apenas usar endereços e constantes pré-compilados.

  • A reflexão também é mais poderosa: você pode recuperar a definição de a protectedou finalmembro, remover a proteção e manipulá-la como se tivesse sido declarada mutável! Obviamente, isso subverte muitas das garantias que a linguagem normalmente oferece para seus programas e pode ser muito, muito perigosa.

E isso explica muito bem quando usá-lo. Normalmente, não. Se você quiser chamar um método, basta chamá-lo. Se você deseja alterar um membro, declare-o mutável em vez de ficar atrás das costas da compilação.

Um uso útil da reflexão no mundo real é ao escrever uma estrutura que precisa interoperar com classes definidas pelo usuário, onde o autor da estrutura não sabe quais serão os membros (ou mesmo as classes). A reflexão lhes permite lidar com qualquer classe sem conhecê-la com antecedência. Por exemplo, eu não acho que seria possível escrever uma biblioteca complexa orientada a aspectos sem reflexão.

As another example, JUnit used to use a trivial bit of reflection: it enumerates all methods in your class, assumes that all those called testXXX are test methods, and executes only those. But this can now be done better with annotations instead, and in fact JUnit 4 has largely moved to annotations instead.


6
"Mais poderoso" precisa de cuidados. Você não precisa de reflexão para obter a integridade de Turing, portanto, nenhum cálculo precisa de reflexão. É claro que Turing complete não diz nada sobre outros tipos de energia, como recursos de E / S e, é claro, reflexão.
precisa saber é o seguinte

11
A reflexão não é necessariamente "muito mais lenta". Você pode usar a reflexão uma vez para gerar um bytecode do wrapper de chamada direta.
SK-logic

2
Você saberá quando precisar. Muitas vezes me perguntei por que (fora da geração da linguagem) alguém precisaria. Então, de repente, eu fiz ... Tive que subir e descer cadeias de pais / filhos para espiar / cutucar dados quando recebi painéis de outros desenvolvedores para aparecer em um dos sistemas que mantenho.
precisa

@ SK-logic: na verdade, para gerar bytecode, você não precisa de reflexão (na verdade a reflexão não contém API para manipulação de bytecode!).
Joachim Sauer

11
@JoachimSauer, é claro, mas você precisará de uma API de reflexão para carregar esse bytecode gerado.
SK-logic

15

Eu era como você uma vez, não sabia muito sobre reflexão - ainda não - mas usei uma vez.

Eu tive uma classe com duas classes internas, e cada classe tinha muitos métodos.

Eu precisava invocar todos os métodos da classe interna, e invocá-los manualmente teria sido muito trabalhoso.

Usando a reflexão, eu poderia invocar todos esses métodos em apenas 2-3 linhas de código, em vez do número dos métodos em si.


4
Por que o voto negativo?
Mahmoud Hossam

11
Upvoting para o preâmbulo
Dmitry Minkovsky

11
@MahmoudHossam Talvez não seja uma prática recomendada, mas sua resposta ilustra uma tática importante que pode ser implementada.
ankush981

13

Eu agruparia os usos da reflexão em três grupos:

  1. Instanciando classes arbitrárias. Por exemplo, em uma estrutura de injeção de dependência, você provavelmente declara que a interface ThingDoer é implementada pela classe NetworkThingDoer. A estrutura encontraria o construtor do NetworkThingDoer e o instanciaria.
  2. Marshalling e unshalling para algum outro formato. Por exemplo, mapeando um objeto com getters e configurações que seguem a convenção de bean para JSON e vice-versa. O código não sabe realmente os nomes dos campos ou dos métodos, apenas examina a classe.
  3. Agrupando uma classe em uma camada de redirecionamento (talvez essa Lista não esteja realmente carregada, mas apenas um ponteiro para algo que saiba como buscá-la no banco de dados) ou falsificando uma classe completamente (o jMock criará uma classe sintética que implementa uma interface para fins de teste).

Esta é a melhor explicação de reflexão que encontrei no StackExchange. A maioria das respostas repete o que o Java Trail diz (que é "você pode acessar todas essas propriedades", mas não por que você faria), fornece exemplos de coisas com reflexões que são muito mais fáceis de ignorar, ou fornece algumas respostas vagas sobre como o Spring usa. Essa resposta, na verdade, fornece três exemplos válidos que NÃO PODEM ser facilmente contornados pela JVM sem reflexão. Obrigado!
Ndm13 11/0118

3

O Reflection permite que um programa trabalhe com código que pode não estar presente e faça isso de maneira confiável.

"Código normal" possui trechos como os URLConnection c = nullquais, por sua simples presença, fazem com que o carregador de classes carregue a classe URLConnection como parte do carregamento dessa classe, lançando uma exceção ClassNotFound e saindo.

O Reflection permite carregar classes com base em seus nomes em forma de cadeia e testá-las para várias propriedades (úteis para várias versões fora de seu controle) antes de iniciar as classes reais que dependem delas. Um exemplo típico é o código específico do OS X usado para fazer com que os programas Java pareçam nativos no OS X, que não estão presentes em outras plataformas.


2

Fundamentalmente, reflexão significa usar o código do seu programa como dados.

Portanto, usar a reflexão pode ser uma boa idéia quando o código do seu programa é uma fonte útil de dados. (Mas existem compensações, portanto nem sempre é uma boa ideia.)

Por exemplo, considere uma classe simples:

public class Foo {
  public int value;
  public string anotherValue;
}

e você deseja gerar XML a partir dele. Você pode escrever um código para gerar o XML:

public XmlNode generateXml(Foo foo) {
  XmlElement root = new XmlElement("Foo");
  XmlElement valueElement = new XmlElement("value");
  valueElement.add(new XmlText(Integer.toString(foo.value)));
  root.add(valueElement);
  XmlElement anotherValueElement = new XmlElement("anotherValue");
  anotherValueElement.add(new XmlText(foo.anotherValue));
  root.add(anotherValueElement);
  return root;
}

Mas isso é muito código clichê, e toda vez que você altera a classe, é necessário atualizar o código. Realmente, você pode descrever o que esse código faz como

  • crie um elemento XML com o nome da classe
  • para cada propriedade da classe
    • crie um elemento XML com o nome da propriedade
    • coloque o valor da propriedade no elemento XML
    • adicione o elemento XML à raiz

Este é um algoritmo, e a entrada do algoritmo é a classe: precisamos do nome e dos nomes, tipos e valores de suas propriedades. É aqui que entra a reflexão: fornece acesso a essas informações. Java permite inspecionar tipos usando os métodos da Classclasse.

Mais alguns casos de uso:

  • definir URLs em um servidor da Web com base nos nomes de métodos de uma classe e parâmetros de URL com base nos argumentos do método
  • converter a estrutura de uma classe em uma definição de tipo GraphQL
  • chama todos os métodos de uma classe cujo nome começa com "test" como um caso de teste de unidade

No entanto, reflexão completa significa não apenas olhar para o código existente (que por si só é conhecido como "introspecção"), mas também modificar ou gerar código. Existem dois casos de uso proeminentes em Java para isso: proxies e zombarias.

Digamos que você tenha uma interface:

public interface Froobnicator {
  void froobnicateFruits(List<Fruit> fruits);
  void froobnicateFuel(Fuel fuel);
  // lots of other things to froobnicate
}

e você tem uma implementação que faz algo interessante:

public class PowerFroobnicator implements Froobnicator {
  // awesome implementations
}

E, de fato, você tem uma segunda implementação também:

public class EnergySaverFroobnicator implements Froobnicator {
  // efficient implementations
}

Agora você também deseja alguma saída de log; você simplesmente deseja uma mensagem de log sempre que um método é chamado. Você pode adicionar saída de log a todos os métodos explicitamente, mas isso seria irritante e você teria que fazer isso duas vezes; uma vez para cada implementação. (Ainda mais quando você adiciona mais implementações.)

Em vez disso, você pode escrever um proxy:

public class LoggingFroobnicator implements Froobnicator {
  private Logger logger;
  private Froobnicator inner;

  // constructor that sets those two

  public void froobnicateFruits(List<Fruit> fruits) {
    logger.logDebug("froobnicateFruits called");
    inner.froobnicateFruits(fruits);
  }

  public void froobnicateFuel(Fuel fuel) {
    logger.logDebug("froobnicateFuel( called");
    inner.froobnicateFuel(fuel);
  }
  // lots of other things to froobnicate
}

Novamente, porém, há um padrão repetitivo que pode ser descrito por um algoritmo:

  • um proxy de logger é uma classe que implementa uma interface
  • ele tem um construtor que leva outra implementação da interface e um logger
  • para cada método na interface
    • a implementação registra uma mensagem "$ methodname chamada"
    • e depois chama o mesmo método na interface interna, transmitindo todos os argumentos

e a entrada desse algoritmo é a definição da interface.

O Reflection permite definir uma nova classe usando esse algoritmo. Java permite fazer isso usando os métodos da java.lang.reflect.Proxyclasse, e existem bibliotecas que oferecem ainda mais poder.

Então, quais são as desvantagens da reflexão?

  • Seu código fica mais difícil de entender. Você é um nível de abstração mais afastado dos efeitos concretos do seu código.
  • Seu código fica mais difícil de depurar. Especialmente nas bibliotecas geradoras de código, o código executado pode não ser o código que você escreveu, mas o código que você gerou e o depurador pode não ser capaz de mostrar esse código (ou permitir que você coloque pontos de interrupção).
  • Seu código fica mais lento. A leitura dinâmica de informações de tipo e o acesso a campos por seus identificadores de tempo de execução, em vez do acesso codificado, é mais lento. A geração dinâmica de código pode atenuar esse efeito, ao custo de ser ainda mais difícil de depurar.
  • Seu código pode se tornar mais frágil. O acesso à reflexão dinâmica não é verificado pelo tipo pelo compilador, mas gera erros no tempo de execução.

1

O Reflection pode manter automaticamente partes do seu programa sincronizadas, onde anteriormente, você precisaria atualizar o programa manualmente para usar as novas interfaces.


5
O preço pago nesse caso é que você perde a verificação de tipo pelo compilador e a segurança de refator no IDE. É uma troca que não estou disposto a fazer.
Barend #
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.