JavaRush /Blog Java /Random-FR /API de réflexion. Réflexion. Le côté obscur de Java
Oleksandr Klymenko
Niveau 13
Харків

API de réflexion. Réflexion. Le côté obscur de Java

Publié dans le groupe Random-FR
Salutations, jeune Padawan. Dans cet article, je vais vous parler de la Force, dont les programmeurs Java n'utilisent le pouvoir que dans une situation apparemment désespérée. Donc, le côté obscur de Java est...Reflection API
API de réflexion.  Réflexion.  Le côté obscur de Java - 1
La réflexion en Java s'effectue à l'aide de l'API Java Reflection. Quelle est cette réflexion ? Il existe une définition courte et précise qui est également populaire sur Internet. La réflexion (du latin tardif reflexio - remonter) est un mécanisme permettant d'étudier les données d'un programme lors de son exécution. Reflection vous permet d'examiner les informations sur les champs, les méthodes et les constructeurs de classe. Le mécanisme de réflexion lui-même vous permet de traiter les types manquants lors de la compilation, mais apparaissant lors de l'exécution du programme. La réflexion et la présence d'un modèle logiquement cohérent de reporting des erreurs permettent de créer un code dynamique correct. En d’autres termes, comprendre le fonctionnement de la réflexion en Java vous ouvre un certain nombre d’opportunités incroyables. Vous pouvez littéralement jongler avec les classes et leurs composants.
API de réflexion.  Réflexion.  Le côté obscur de Java - 2
Voici une liste de base de ce que la réflexion permet :
  • Connaître/déterminer la classe d'un objet ;
  • Obtenez des informations sur les modificateurs de classe, les champs, les méthodes, les constantes, les constructeurs et les superclasses ;
  • Découvrez quelles méthodes appartiennent à la ou aux interfaces implémentées ;
  • Créez une instance d'une classe et le nom de la classe est inconnu jusqu'à ce que le programme soit exécuté ;
  • Obtenez et définissez la valeur d'un champ d'objet par son nom ;
  • Appelez la méthode d'un objet par son nom.
La réflexion est utilisée dans presque toutes les technologies Java modernes. Il est difficile d'imaginer si Java, en tant que plate-forme, aurait pu atteindre une telle adoption sans réflexion. Très probablement, je ne pourrais pas. Vous êtes familiarisé avec l’idée théorique générale de la réflexion, passons maintenant à son application pratique ! Nous n’étudierons pas toutes les méthodes de l’API Reflection, seulement ce que l’on rencontre réellement en pratique. Puisque le mécanisme de réflexion implique de travailler avec des classes, nous aurons une classe simple -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);
   }
}
Comme nous pouvons le constater, c’est la classe la plus courante. Le constructeur avec les paramètres est commenté pour une raison, nous y reviendrons plus tard. Si vous avez regardé attentivement le contenu du cours, vous avez probablement remarqué l'absence de getter« a » pour le name. Le champ lui-même nameest marqué d’un modificateur d’accèsprivate ; nous ne pourrons pas y accéder en dehors de la classe elle-même ; =>nous ne pouvons pas obtenir sa valeur. "Donc quel est le problème? - vous dites. "Ajouter getterou modifier le modificateur d'accès." Et vous aurez raison, mais que se passe-t-il si MyClassc'est dans une bibliothèque aar compilée ou dans un autre module fermé sans accès en édition, et en pratique, cela arrive extrêmement souvent. Et un programmeur inattentif a simplement oublié d'écrire getter. Il est temps de penser à la réflexion ! Essayons d'accéder au privatechamp nameclass 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
}
Voyons maintenant ce qui s'est passé ici. Il existe une merveilleuse classe en Java Class. Il représente des classes et des interfaces dans une application Java exécutable. Nous n'aborderons pas le lien entre Classet ClassLoader. ce n'est pas le sujet de l'article. Ensuite, pour obtenir les champs de cette classe, vous devez appeler la méthode getFields(), cette méthode nous renverra tous les champs disponibles de la classe. Cela ne nous convient pas, puisque notre champ est private, nous utilisons donc la méthode getDeclaredFields(). Cette méthode renvoie également un tableau de champs de classe, mais maintenant les deux privateet protected. Dans notre situation, nous connaissons le nom du champ qui nous intéresse, et nous pouvons utiliser la méthode getDeclaredField(String), où Stringest le nom du champ souhaité. Note: getFields()et getDeclaredFields()ne renvoie pas les champs de la classe parent ! Super, nous avons reçu un objet Field avec un lien vers notre name. Parce que le champ n'était pas публичным(public), l'accès doit être donné pour travailler avec lui. La méthode setAccessible(true)nous permet de continuer à travailler. Désormais, le terrain nameest entièrement sous notre contrôle ! Vous pouvez obtenir sa valeur en appelant get(Object)l'objet Field, où Objectse trouve une instance de notre classe MyClass. Nous le castons Stringet l'attribuons à notre variable name. Au cas où nous n'aurions soudainement plus setter'a, nous pouvons utiliser la méthode pour définir une nouvelle valeur pour le champ name set:
field.set(myClass, (String) "new value");
Toutes nos félicitations! Vous venez de maîtriser le mécanisme de base de la réflexion et avez pu accéder au privateterrain ! Faites attention au bloc try/catchet aux types d’exceptions gérées. L'IDE lui-même indiquera leur présence obligatoire, mais leur nom indique clairement pourquoi ils sont ici. Poursuivre! Comme vous l'avez peut-être remarqué, la nôtre MyClassdispose déjà d'une méthode pour afficher des informations sur les données de classe :
private void printData(){
       System.out.println(number + name);
   }
Mais ce programmeur a laissé ici aussi un héritage. La méthode est sous le modificateur d'accès privateet nous avons dû écrire nous-mêmes le code de sortie à chaque fois. Ce n’est pas dans les règles, où est notre réflexion ?... Écrivons la fonction suivante :
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();
   }
}
Ici, la procédure est à peu près la même que pour obtenir un champ - nous obtenons la méthode souhaitée par son nom et y donnons accès. Et pour appeler l'objet Methodque nous utilisons invoke(Оbject, Args), où Оbjectse trouve également une instance de la classe MyClass. Args- arguments de méthode - le nôtre n'en a pas. Nous utilisons maintenant la fonction pour afficher des informations 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
}
Hourra, nous avons maintenant accès à la méthode privée de la classe. Mais que se passe-t-il si la méthode a encore des arguments, et pourquoi y a-t-il un constructeur commenté ? Chaque chose en son temps. Dès la définition du début, il est clair que la réflexion permet de créer des instances d'une classe dans un mode runtime(pendant que le programme est en cours d'exécution) ! Nous pouvons créer un objet d'une classe par le nom complet de cette classe. Le nom de classe complet est le nom de la classe, étant donné son chemin d'accès dans package.
API de réflexion.  Réflexion.  Le côté obscur de Java - 3
Dans ma hiérarchie, packagele nom complet MyClasssera «reflection.MyClass ». Vous pouvez également connaître le nom de la classe de manière simple (il renverra le nom de la classe sous forme de chaîne) :
MyClass.class.getName()
Créons une instance de la classe en utilisant la réflexion :
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
}
Au moment du démarrage de l'application Java, toutes les classes ne sont pas chargées dans la JVM. Si votre code ne fait pas référence à la classe MyClass, alors la personne responsable du chargement des classes dans la JVM, c'est-à-dire ClassLoader, ne le chargera jamais là-bas. Nous devons donc ClassLoaderle forcer à se charger et recevoir une description de notre classe sous la forme d'une variable de type Class. Pour cette tâche, il existe une méthode forName(String), où Stringest le nom de la classe dont nous avons besoin de la description. Après avoir reçu Сlass, l'appel de méthode newInstance()retournera Object, qui sera créé selon la même description. Reste à apporter cet objet à notre classe MyClass. Cool! C'était difficile, mais j'espère que c'est compréhensible. Nous pouvons désormais créer une instance d’une classe littéralement à partir d’une seule ligne ! Malheureusement, la méthode décrite ne fonctionnera qu'avec le constructeur par défaut (sans paramètres). Comment appeler des méthodes avec des arguments et des constructeurs avec des paramètres ? Il est temps de décommenter notre constructeur. Comme prévu, newInstance()il ne trouve pas le constructeur par défaut et ne fonctionne plus. Réécrivons la création d'une instance 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
}
Pour obtenir les constructeurs de classe, appelez la méthode à partir de la description de la classe getConstructors(), et pour obtenir les paramètres du constructeur, appelezgetParameterTypes() :
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
De cette façon, nous obtenons tous les constructeurs et tous leurs paramètres. Dans mon exemple, il y a un appel à un constructeur spécifique avec des paramètres spécifiques déjà connus. Et pour appeler ce constructeur, nous utilisons la méthode newInstance, dans laquelle nous spécifions les valeurs de ces paramètres. La même chose se produira invokepour l’appel des méthodes. La question se pose : où l’appel réflexif des constructeurs peut-il être utile ? Les technologies Java modernes, comme mentionné au début, ne peuvent pas se passer de l'API Reflection. Par exemple, DI (Dependency Injection), où les annotations combinées à la réflexion des méthodes et des constructeurs forment la bibliothèque Dagger, populaire dans le développement Android. Après avoir lu cet article, vous pouvez en toute confiance vous considérer comme éclairé sur les mécanismes de l’API Reflection. Ce n’est pas pour rien que la réflexion est appelée le côté obscur de Java. Cela brise complètement le paradigme de la POO. En Java, l'encapsulation sert à masquer et à limiter l'accès de certains composants du programme à d'autres. En utilisant le modificateur privé, nous entendons que l'accès à ce champ se fera uniquement dans la classe où ce champ existe, sur cette base, nous construisons l'architecture ultérieure du programme. Dans cet article, nous avons vu comment utiliser la réflexion pour arriver n’importe où. Un bon exemple sous la forme d'une solution architecturale est le modèle de conception générative - Singleton. Son idée principale est que pendant tout le fonctionnement du programme, la classe qui implémente ce modèle ne doit en avoir qu'une seule copie. Cela se fait en définissant le modificateur d'accès par défaut sur privé pour le constructeur. Et ce serait très grave si un programmeur avec sa propre réflexion créait de telles classes. À propos, il y a une question très intéressante que j'ai récemment entendue de la part de mon employé : une classe qui implémente un modèle peut-elle avoir Singletondes héritiers ? Est-il possible que même la réflexion soit impuissante dans ce cas ? Écrivez votre retour sur l'article et la réponse dans les commentaires, et posez également vos questions ! La véritable puissance de l'API Reflection vient en combinaison avec les annotations d'exécution, dont nous parlerons probablement dans un prochain article sur le côté obscur de Java. Merci pour votre attention!
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION