JavaRush /Blogue Java /Random-PT /API de reflexão. Reflexão. O lado negro de Java
Oleksandr Klymenko
Nível 13
Харків

API de reflexão. Reflexão. O lado negro de Java

Publicado no grupo Random-PT
Saudações, jovem Padawan. Neste artigo vou falar sobre a Força, cujo poder os programadores Java usam apenas em uma situação aparentemente desesperadora. Então, o lado negro do Java é -Reflection API
API de reflexão.  Reflexão.  O lado negro de Java - 1
A reflexão em Java é feita usando a API Java Reflection. O que é essa reflexão? Existe uma definição curta e precisa que também é popular na Internet. Reflexão (do latim tardio reflexio - voltar atrás) é um mecanismo para estudar dados sobre um programa durante sua execução. O Reflection permite examinar informações sobre campos, métodos e construtores de classes. O próprio mecanismo de reflexão permite processar tipos que faltam durante a compilação, mas aparecem durante a execução do programa. A reflexão e a presença de um modelo logicamente coerente para reportar erros possibilitam a criação de código dinâmico correto. Em outras palavras, entender como a reflexão funciona em java abre uma série de oportunidades incríveis para você. Você pode literalmente fazer malabarismos com classes e seus componentes.
API de reflexão.  Reflexão.  O lado negro de Java - 2
Aqui está uma lista básica do que a reflexão permite:
  • Descubra/determine a classe de um objeto;
  • Obtenha informações sobre modificadores de classe, campos, métodos, constantes, construtores e superclasses;
  • Descubra quais métodos pertencem à interface/interfaces implementadas;
  • Crie uma instância de uma classe e o nome da classe será desconhecido até que o programa seja executado;
  • Obtenha e defina o valor de um campo de objeto por nome;
  • Chame o método de um objeto pelo nome.
A reflexão é usada em quase todas as tecnologias Java modernas. É difícil imaginar se o Java como plataforma poderia ter alcançado uma adoção tão grande sem reflexão. Muito provavelmente eu não conseguiria. Você já se familiarizou com a ideia teórica geral de reflexão, agora vamos à sua aplicação prática! Não estudaremos todos os métodos da API Reflection, apenas o que realmente é encontrado na prática. Como o mecanismo de reflexão envolve trabalhar com classes, teremos uma classe simples - MyClass:
public class MyClass {
   private int number;
   private String name = "default";
//    public MyClass(int number, String name) {
//        this.number = number;
//        this.name = name;
//    }
   public int getNumber() {
       return number;
   }
   public void setNumber(int number) {
       this.number = number;
   }
   public void setName(String name) {
       this.name = name;
   }
   private void printData(){
       System.out.println(number + name);
   }
}
Como podemos ver, esta é a classe mais comum. O construtor com parâmetros está comentado por um motivo, voltaremos a isso mais tarde. Se você olhou atentamente o conteúdo da aula, provavelmente notou a ausência de getter'a para o name. O campo em si nameestá marcado com um modificador de acesso private; não poderemos acessá-lo fora da própria classe; =>não poderemos obter seu valor. "Então qual é o problema? - você diz. “Adicione getterou altere o modificador de acesso.” E você terá razão, mas e se MyClassestiver em uma biblioteca aar compilada ou em outro módulo fechado sem acesso de edição, e na prática isso acontece com muita frequência. E algum programador desatento simplesmente se esqueceu de escrever getter. É hora de lembrar da reflexão! Vamos tentar chegar ao privatecampo nameda classe MyClass:
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //no getter =(
   System.out.println(number + name);//output 0null
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(number + name);//output 0default
}
Vamos descobrir o que aconteceu aqui agora. Existe uma aula maravilhosa em java Class. Representa classes e interfaces em um aplicativo Java executável. Não tocaremos na conexão entre Classe ClassLoader. este não é o tema do artigo. A seguir, para obter os campos desta classe, é necessário chamar o método getFields(), este método nos retornará todos os campos disponíveis da classe. Isso não é adequado para nós, já que nosso campo é private, então usamos o método getDeclaredFields().Este método também retorna um array de campos de classe, mas agora ambos privatee protected. Na nossa situação, sabemos o nome do campo que nos interessa, e podemos utilizar o método getDeclaredField(String), onde Stringestá o nome do campo desejado. Observação: getFields()e getDeclaredFields()não retorne os campos da classe pai! Ótimo, recebemos um objeto Field com um link para nosso arquivo name. Porque o campo não era публичным(público), deveria ser dado acesso para trabalhar com ele. O método setAccessible(true)nos permite continuar trabalhando. Agora o campo nameestá completamente sob nosso controle! Você pode obter seu valor chamando get(Object)o objeto Field, onde Objectestá uma instância de nossa classe MyClass. Nós o lançamos Stringe o atribuímos à nossa variável name. Caso de repente não tenhamos setter'a, podemos usar o método para definir um novo valor para o campo de nome set:
field.set(myClass, (String) "new value");
Parabéns! Você acabou de dominar o mecanismo básico de reflexão e conseguiu acessar o privatecampo! Preste atenção ao bloco try/catche aos tipos de exceções tratadas. O próprio IDE indicará sua presença obrigatória, mas seu nome deixa claro por que estão aqui. Vá em frente! Como você deve ter notado, o nosso MyClassjá possui um método para exibir informações sobre os dados da classe:
private void printData(){
       System.out.println(number + name);
   }
Mas este programador também deixou um legado aqui. O método está sob o modificador de acesso privatee tivemos que escrever o código de saída todas as vezes. Não está em ordem, onde está o nosso reflexo?... Vamos escrever a seguinte função:
public static void printData(Object myClass){
   try {
       Method method = myClass.getClass().getDeclaredMethod("printData");
       method.setAccessible(true);
       method.invoke(myClass);
   } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
       e.printStackTrace();
   }
}
Aqui o procedimento é aproximadamente o mesmo de obter um campo - obtemos o método desejado pelo nome e damos acesso a ele. E para chamar o objeto Methodque usamos invoke(Оbject, Args), onde Оbjecttambém está uma instância da classe MyClass. Args- argumentos do método - o nosso não possui nenhum. Agora usamos a função para exibir informações printData:
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // outout 0default
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       field.set(myClass, (String) "new value");
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   printData(myClass);// output 0new value
}
Viva, agora temos acesso ao método privado da classe. Mas e se o método ainda tiver argumentos e por que existe um construtor comentado? Tudo tem o seu tempo. Pela definição inicial fica claro que a reflexão permite criar instâncias de uma classe em um modo runtime(enquanto o programa está em execução)! Podemos criar um objeto de uma classe pelo nome totalmente qualificado dessa classe. O nome completo da classe é o nome da classe, considerando o caminho para ela no arquivo package.
API de reflexão.  Reflexão.  O Lado Negro de Java - 3
Na minha hierarquia, packageo nome completo MyClassserá “ reflection.MyClass”. Você também pode descobrir o nome da classe de uma forma simples (ele retornará o nome da classe como uma string):
MyClass.class.getName()
Vamos criar uma instância da classe usando reflexão:
public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       myClass = (MyClass) clazz.newInstance();
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}
No momento em que o aplicativo Java é iniciado, nem todas as classes são carregadas na JVM. Se o seu código não se referir à classe MyClass, então quem for responsável por carregar as classes na JVM, ou seja ClassLoader, nunca irá carregá-lo lá. Portanto, precisamos forçá- ClassLoaderlo a carregar e receber uma descrição da nossa classe na forma de uma variável do tipo Class. Para esta tarefa existe um método forName(String), onde Stringestá o nome da classe cuja descrição necessitamos. Tendo recebido Сlass, newInstance()retornará a chamada do método Object, que será criada de acordo com a mesma descrição. Resta trazer esse objeto para nossa classe MyClass. Legal! Foi difícil, mas espero que seja compreensível. Agora podemos criar uma instância de uma classe literalmente a partir de uma linha! Infelizmente, o método descrito só funcionará com o construtor padrão (sem parâmetros). Como chamar métodos com argumentos e construtores com parâmetros? É hora de descomentar nosso construtor. Como esperado, newInstance()ele não encontra o construtor padrão e não funciona mais. Vamos reescrever a criação de uma instância de classe:
public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       Class[] params = {int.class, String.class};
       myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}
Para obter construtores de classe, chame o método da descrição da classe getConstructors()e, para obter parâmetros do construtor, chame getParameterTypes():
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
Desta forma obtemos todos os construtores e todos os parâmetros para eles. No meu exemplo, há uma chamada para um construtor específico com parâmetros específicos e já conhecidos. E para chamar esse construtor, utilizamos o método newInstance, no qual especificamos os valores desses parâmetros. O mesmo acontecerá invokecom a chamada de métodos. Surge a pergunta: onde a chamada reflexiva de construtores pode ser útil? As tecnologias Java modernas, conforme mencionado no início, não podem prescindir da API Reflection. Por exemplo, DI (Injeção de Dependência), onde anotações combinadas com reflexão de métodos e construtores formam a biblioteca Dagger, popular no desenvolvimento Android. Depois de ler este artigo, você pode se considerar confiante nos mecanismos da API Reflection. Não é à toa que a reflexão é chamada de lado negro do java. Isso quebra completamente o paradigma OOP. Em java, o encapsulamento serve para ocultar e limitar o acesso de alguns componentes do programa a outros. Ao usar o modificador private queremos dizer que o acesso a este campo estará apenas dentro da classe onde este campo existe, com base nisso construímos a arquitetura posterior do programa. Neste artigo, vimos como você pode usar a reflexão para chegar a qualquer lugar. Um bom exemplo na forma de solução arquitetônica é o padrão de design generativo - Singleton. Sua ideia principal é que durante todo o funcionamento do programa, a classe que implementa este template tenha apenas uma cópia. Isso é feito definindo o modificador de acesso padrão como privado para o construtor. E será muito ruim se algum programador, com sua própria reflexão, criar tais classes. Aliás, há uma pergunta muito interessante que ouvi recentemente do meu funcionário: uma classe que implementa um template pode ter Singletonherdeiros? É possível que mesmo a reflexão seja impotente neste caso? Escreva sua opinião sobre o artigo e a resposta nos comentários, e também tire suas dúvidas! O verdadeiro poder da API Reflection vem em combinação com Runtime Annotations, sobre as quais provavelmente falaremos em um artigo futuro sobre o lado negro do Java. Obrigado pela sua atenção!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION