Salutations, jeune Padawan. Dans cet article, je vais vous parler de la Force, un pouvoir que les programmeurs Java n'utilisent que dans des situations apparemment impossibles. Le côté obscur de Java est l'API Reflection. En Java, la réflexion est implémentée à l'aide de l'API Java Reflection.
Dans ma hiérarchie de packages , le nom complet de MyClass serait "reflection.MyClass". Il existe également un moyen simple d'apprendre le nom d'une classe (renvoyez le nom de la classe sous forme de chaîne):
Qu'est-ce que la réflexion Java ?
Il existe une définition courte, précise et populaire sur Internet. La réflexion ( du latin reflexio - revenir en arrière ) est un mécanisme permettant d'explorer les données d'un programme pendant son exécution. La réflexion vous permet d'explorer des informations sur les champs, les méthodes et les constructeurs de classe. La réflexion vous permet de travailler avec des types qui n'étaient pas présents au moment de la compilation, mais qui sont devenus disponibles au moment de l'exécution. La réflexion et un modèle logiquement cohérent pour l'émission d'informations d'erreur permettent de créer un code dynamique correct. En d'autres termes, comprendre comment fonctionne la réflexion en Java vous ouvrira un certain nombre d'opportunités incroyables. Vous pouvez littéralement jongler avec les classes et leurs composants. Voici une liste de base de ce que la réflexion permet :- Apprendre/déterminer la classe d'un objet ;
- Obtenir des informations sur les modificateurs, les champs, les méthodes, les constantes, les constructeurs et les superclasses d'une classe ;
- Découvrez quelles méthodes appartiennent aux interfaces implémentées ;
- Créez une instance d'une classe dont le nom de classe est inconnu jusqu'à l'exécution ;
- Obtenir et définir les valeurs des champs d'un objet par nom ;
- Appelez la méthode d'un objet par son nom.
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 vous pouvez le voir, c'est une classe très basique. Le constructeur avec paramètres est délibérément commenté. Nous y reviendrons plus tard. Si vous avez regardé attentivement le contenu de la classe, vous avez probablement remarqué l'absence de getter pour le champ name . Le champ de nom lui-même est marqué avec le modificateur d'accès privé : nous ne pouvons pas y accéder en dehors de la classe elle-même, ce qui signifie que nous ne pouvons pas récupérer sa valeur. « Alors quel est le problème ? vous dites. "Ajouter un getter ou modifier le modificateur d'accès". Et vous auriez raison, à moins queMyClass
se trouvait dans une bibliothèque AAR compilée ou dans un autre module privé sans possibilité d'apporter des modifications. En pratique, cela arrive tout le temps. Et un programmeur négligent a tout simplement oublié d'écrire un getter . C'est le moment même de se souvenir de la réflexion ! Essayons d'accéder au champ de nom privé de la MyClass
classe :
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
}
Analysons ce qui vient de se passer. En Java, il existe une merveilleuse classe appelée Class
. Il représente des classes et des interfaces dans une application Java exécutable. Nous n'aborderons pas la relation entre Class
et ClassLoader
, puisque ce n'est pas le sujet de cet article. Ensuite, pour récupérer les champs de cette classe, vous devez appeler la getFields()
méthode. Cette méthode renverra tous les champs accessibles de cette classe. Cela ne fonctionne pas pour nous, car notre champ est private , nous utilisons donc la getDeclaredFields()
méthode. Cette méthode renvoie également un tableau de champs de classe, mais elle inclut désormais des champs privés et protégés . Dans ce cas, nous connaissons le nom du champ qui nous intéresse, nous pouvons donc utiliser la getDeclaredField(String)
méthode, oùString
est le nom du champ souhaité. Note: getFields()
et getDeclaredFields()
ne renvoie pas les champs d'une classe parente ! Super. Nous avons un Field
objet faisant référence à notre nom . Étant donné que le champ n'était pas public , nous devons accorder l'accès pour travailler avec lui. La setAccessible(true)
méthode permet d'aller plus loin. Maintenant, le champ du nom est sous notre contrôle total ! Vous pouvez récupérer sa valeur en appelant la méthode Field
de l'objet , où est une instance de notre classe. Nous convertissons le type en et attribuons la valeur à notre variable de nom . Si nous ne trouvons pas de setter pour définir une nouvelle valeur dans le champ name, vous pouvez utiliser la méthode set :get(Object)
Object
MyClass
String
field.set(myClass, (String) "new value");
Toutes nos félicitations! Vous venez de maîtriser les bases de la réflexion et d'accéder à un champ privé ! Faites attention au try/catch
bloc et aux types d'exceptions gérées. L'IDE vous dira que leur présence est requise par elle-même, mais vous pouvez clairement dire par leur nom pourquoi ils sont ici. Passons à autre chose ! Comme vous l'avez peut-être remarqué, notre MyClass
classe dispose déjà d'une méthode pour afficher des informations sur les données de la classe :
private void printData(){
System.out.println(number + name);
}
Mais ce programmeur a laissé ses empreintes digitales ici aussi. La méthode a un modificateur d'accès privé et nous devons écrire notre propre code pour afficher les données à chaque fois. Quel bordel. Où est passé notre reflet ? Écrivez 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();
}
}
La procédure ici est à peu près la même que celle utilisée pour récupérer un champ. Nous accédons à la méthode souhaitée par son nom et lui accordons l'accès. Et sur l' Method
objet, nous appelons la invoke(Object, Args)
méthode, où Object
est également une instance de la MyClass
classe. Args
sont les arguments de la méthode, bien que la nôtre n'en ait pas. Maintenant, nous utilisons la printData
fonction pour afficher des informations :
public static void main(String[] args) {
MyClass myClass = new MyClass();
int number = myClass.getNumber();
String name = null; //?
printData(myClass); // Output: 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 des arguments, et pourquoi le constructeur est-il commenté ? Tout en son temps. Il ressort clairement de la définition au début que la réflexion vous permet de créer des instances d'une classe au moment de l'exécution (pendant que le programme est en cours d'exécution) ! Nous pouvons créer un objet en utilisant le nom complet de la classe. Le nom complet de la classe est le nom de la classe, y compris le chemin de son package .

MyClass.class.getName()
Utilisons la réflexion Java pour créer une instance de la classe :
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
}
Lorsqu'une application Java démarre, toutes les classes ne sont pas chargées dans la JVM. Si votre code ne fait pas référence à la MyClass
classe, alors ClassLoader
, qui est responsable du chargement des classes dans la JVM, ne chargera jamais la classe. Cela signifie que vous devez forcer ClassLoader
pour le charger et obtenir une description de classe sous la forme d'une Class
variable. C'est pourquoi nous avons la forName(String)
méthode, où String
est le nom de la classe dont nous avons besoin de la description. Après avoir obtenu l' Сlass
objet, l'appel de la méthode newInstance()
renverra un Object
objet créé à l'aide de cette description. Il ne reste plus qu'à fournir cet objet à notreMyClass
classe. Cool! C'était difficile, mais compréhensible, j'espère. Nous pouvons maintenant créer une instance d'une classe en littéralement une seule ligne ! Malheureusement, l'approche décrite ne fonctionnera qu'avec le constructeur par défaut (sans paramètres). Comment appeler des méthodes et des constructeurs avec des paramètres ? Il est temps de décommenter notre constructeur. Comme prévu, newInstance()
impossible de trouver le constructeur par défaut et ne fonctionne plus. Réécrivons l'instanciation 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
}
La getConstructors()
méthode doit être appelée sur la définition de classe pour obtenir des constructeurs de classe, puis getParameterTypes()
doit être appelée pour obtenir les paramètres d'un constructeur :
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
Class[] paramTypes = constructor.getParameterTypes();
for (Class paramType : paramTypes) {
System.out.print(paramType.getName() + " ");
}
System.out.println();
}
Cela nous donne tous les constructeurs et leurs paramètres. Dans mon exemple, je fais référence à un constructeur spécifique avec des paramètres spécifiques, déjà connus. Et pour appeler ce constructeur, on utilise la newInstance
méthode, à laquelle on passe les valeurs de ces paramètres. Il en sera de même lors de l'utilisation invoke
pour appeler des méthodes. Cela soulève la question suivante : quand l'appel de constructeurs par réflexion est-il utile ? Comme déjà mentionné au début, les technologies Java modernes ne peuvent pas se passer de l'API Java Reflection. Par exemple, Dependency Injection (DI), qui combine des annotations avec la réflexion de méthodes et de constructeurs pour former le populaire Darerbibliothèque pour le développement Android. Après avoir lu cet article, vous pouvez vous considérer en toute confiance comme formé à l'API Java Reflection. Ils n'appellent pas la réflexion le côté obscur de Java pour rien. Cela brise complètement le paradigme de la POO. En Java, l'encapsulation masque et restreint l'accès des autres à certains composants du programme. Lorsque nous utilisons le modificateur privé, nous souhaitons que ce champ ne soit accessible qu'à partir de la classe où il existe. Et nous construisons l'architecture ultérieure du programme sur la base de ce principe. Dans cet article, nous avons vu comment vous pouvez utiliser la réflexion pour vous frayer un chemin n'importe où. Le design pattern créationnel Singletonen est un bon exemple en tant que solution architecturale. L'idée de base est qu'une classe implémentant ce modèle n'aura qu'une seule instance lors de l'exécution de l'ensemble du programme. Ceci est accompli en ajoutant le modificateur d'accès privé au constructeur par défaut. Et ce serait très mauvais si un programmeur utilisait la réflexion pour créer plus d'instances de telles classes. Au fait, j'ai récemment entendu un collègue poser une question très intéressante : une classe qui implémente le modèle Singleton peut-elle être héritée ? Se pourrait-il que, dans ce cas, même la réflexion soit impuissante ? Laissez vos commentaires sur l'article et votre réponse dans les commentaires ci-dessous, et posez-y vos propres questions !
Plus de lecture : |
---|
GO TO FULL VERSION