La réflexion - programmer en java, programme java, cours

publicité
La réflexion
Chapitres traités
La classe Class
Dans cette étude, nous allons nous pencher sur l'API de réflexion de Java, gérée par les classes du paquetage java.lang.reflect. Comme
son nom l'indique, la reflexion est la possibilité, pour une classe ou un objet, de s'examiner. Elle permet au code Java d'étudier un objet
(plus précisément la classe de l'objet) et de déterminer sa structure. Dans les limites imposées par le gestionnaire de sécurité, nous
pouvons trouver les constructeurs, les méthodes, et les attributs d'une classe ainsi que leurs valeurs.
Nous pouvons même changer la valeur des attributs, invoquer dynamiquement des méthodes, et construire de nouveaux objets, comme
si Java possédait des pointeurs sur les attributs et les méthodes.
Nous pouvons réaliser tout cela sur des objets que notre code n'a pas encore vu.
.
La bibliothèque de reflexion constitue une boîte à outils riche et élaborée pour écrire des programmes qui manipules dynamiquement du code Java. Cette
fonctionnalité est très largement utilisée dans JavaBeans, l'architecture des composants Java. Au moyen de la reflexion, Java est capable de supporter des outils
comme ceux auxquels les utilisateurs de Visual Basic sont habitués. Précisément, lorsque de nouvelles classes sont ajoutées au moment de la conception ou de
l'exécution, des outils de développement d'application rapide peuvent se renseigner dynamiquement sur les capacités des classes qui ont été ajoutées.
Une programme qui peut analyser les capacités des classes est appelé réflecteur.
.
Détermination de la classe d'un objet au moyen de la classe Class
Avant de déterminer les éléments de la classe, comme les attributs, les méthodes et les constructeurs, il faut préalablement connaître le nom même de la classe d'un objet.
Dans ce chapitre, nous allons voir comment récupérer cette information.
Lorsque le programme est lancé, le système d'exécution de Java gère, pour tous les objets, ce que l'on appelle "L'identification de type à l'exécution". Cette
information mémorise la classe à laquelle chaque objet appartient. L'information de type au moment de l'exécution est employée par la machine virtuelle pour
sélectionner les méthodes correctes à exécuter.
Obtenir une classe au moyen de la méthode getClass()
Vous pouvez accéder à cette information en travaillant avec une classe Java particulière, baptisée singulièrement Class. La méthode getClass() de la classe Object
renvoie une instance de type Class :
Tout comme un objet Personne décrit les propriétés d'une personne particulier, un objet Class décrit les propriétés d'une classe particulière. La méthode la plus
utilisée de Class est sans contexte getName(), qui renvoie le nom de la classe :
Résultat
package test;
public class Main {
public static void main(String[] args) {
Personne p = new Personne("REMY", "Emmanuel");
Class cl = p.getClass();
System.out.println(cl.getName());
}
}
class Personne {
private String nom, prénom;
public Personne(String nom, String prénom) {
this.nom = nom;
this.prénom = prénom;
}
}
init:
deps-jar:
Compiling 1 source file to C:\netbeans5.0\travail\Test\build\classes
compile:
run:
test.Personne
BUILD SUCCESSFUL (total time: 3 seconds)
Obtenir une classe au moyen de la méthode forName()
Vous pouvez aussi obtenir un objet Class correspondant à une chaîne de caractères, à l'aide de la méthode statique forName() :
Résultat
package test;
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
String nomClasse = "test.Personne";
Class cl = Class.forName(nomClasse);
System.out.println(cl.getName());
}
}
class Personne {
private String nom, prénom;
public Personne(String nom, String prénom) {
this.nom = nom;
this.prénom = prénom;
init:
deps-jar:
Compiling 1 source file to C:\netbeans5.0\travail\Test\build\classes
compile:
run:
test.Personne
BUILD SUCCESSFUL (total time: 3 seconds)
}
}
Cette technique est utilisée si le nom de la classe est stockée dans une chaîne de caractères qui peut changer à l'exécution. Cela fonctionne lorsque nomClasse est
bien le nom d'une classe ou d'une interface. Dans le cas contraire, la méthode forName() lance une exception vérifiée.
Obtenir une classe au moyen du suffixe .class
Une troisième technique emploie un raccourci pratique pour obtenir un objet de type Class. En effet, si T est d'un type Java quelconque, alors T.class représente
l'objet classe correspondant.
Class cl1 = Personne.class ; // Pour le suffixe .class il faut importer java.util.*;
Class cl2 = int.class ;
Class cl3 = Double[].class ;
Remarquez qu'un objet Class désigne en réalité un type, qui n'est pas nécessairement une classe. Par exemple, int n'est pas une classe, mais int.class est pourtant
un objet de type Class.
Détermination des caractéristiques d'une classe
Les trois premières caractéristiques d'une classe sont ses attributs (ou champs), ses méthodes et ses constructeurs.
Pour des raisons de description ou d'accès à un objet, elles sont représentées dans l'API de reflexion par des classes
séparées. Nous pouvont rechercher ces membres de classe en utilisant l'objet Class :
1. java.lang.reflect.Field : pour les champs (attributs).
2. java.lang.reflect.Method : pour les méthodes.
3. java.lang.reflect.Constructor : pour les constructor.
La classe Class
Field[] getFields()
Obtient tous les attributs publics, y
compris ceux hérités. Cette méthode
renvoie un tableau contenant les objets
Field représentant les attributs publics
de cette classe ou de ses superclasses.
Cette méthode renvoie un tableau de
longueur 0 s'il n'existe pas d'attribut
correspondant
ou
si
l'objet
Class
représente un type primitif ou un type
tableau.
Field getField(String nom)
Obtient l'attribut public indiqué, qui peut
être hérité.
Field[] getDeclaredFields()
Obtient tous les attributs publics ou non
publics déclarée dans cette classe (non
compris ceux hérités des classes mères).
Cette méthode renvoie un tableau
d'objets Field pour tous les attributs de
cette classe. Elle renvoie un tableau de
longueur 0 s'il n'existe pas d'attribut
correspondant
ou
si
l'objet
Class
représente un type primitif ou un type
tableau.
La classe Class propose deux paires de méthodes permettant d'atteindre chaque caractéristiques. Une paire
autorise l'accès aux fonctionnalités publiques d'une classe (y compris celles héritées de ses classes mères), l'autre
permettant l'accès à tout élément public ou non, déclaré directement à l'intérieur de la classe (mais pas aux
fonctionnalités hérités), selon les considérations de sécurité. Voici quelques exemples :
1. getFields() : renvoie un tableau d'objets Field représentant tous les attributs (champs) publiques d'une classe, y
compris celles héritées ;
2. getDeclaredFields() : renvoie un tableau représentant toutes les variables déclarées dans la classe, quels que
soient leurs modificateurs d'accès, mais non comprises les attributs hérités ;
3. Pour les constructeurs, la distinction entre "tous les constructeurs" et "les constructeurs déclarés" n'est pas
significative (les classes n'héritent pas des constructeurs). Par conséquent, getConstructors() et
getDeclaredConstructors() ne diffèrent que par le fait que la première renvoie les constructeurs publics et la
seconde tous les constructeurs de la classe.
Chaque paire de méthodes comporte une méthode permettant de lister tous les éléments à la fois, par exemple
getFields(), et une méthode permettant de rechercher un élément particulier par son nom et, pour les méthodes et
les constructeurs, par signature, par exemple, getField(), qui prend le nom de l'attribut comme argument.
Field getDeclaredField(String nom)
Obtient l'attribut indiqué, public ou non
public, déclaré dans cette classe (les
attributs ne sont pas pris en compte).
Method[] getMethods()
Obtient toutes les méthodes publiques, y
compris celles héritées. Cette méthode
renvoie un tableau qui contient des
objets
Method.
Elle
renvoie
des
méthodes
publiques
et
inclut
les
méthodes héritées.
Method getMethod(String
types)
nom,
Class...
Obtient la méthode publique indiquée
dont les arguments correspondent aux
types indiqués dans types. La méthode
peut être héritée.
Method[] getDeclaredMethods()
La reflexion pour analyser les caractéristiques d'une classe
Les trois classes Field, Method, Constructor, qui se trouvent dans le paquetage java.lang.reflect, décrivent
respectivement les attributs, les méthodes et les constructeurs d'une classe. Ces trois classes disposent d'une méthode
getName() qui renvoie le nom de l'élément.
1. La classe Field possède une méthode getType() renvoyant un objet, de type Class, qui décrit le type de l'attribut.
2. Les classes Method et Constructor possèdent des méthodes permettant d'obtenir les types des paramètres, et
la classe Method signale aussi le type de retour.
Obtient toutes les méthodes publiques et
non publiques de la classe (non
comprises celles héritées des classes
mères). Cette méthode renvoie un
tableau qui contient des objets Method.
Elle renvoie toutes les méthodes de cette
classe ou interface mais n'inclut pas les
méthodes héritées.
Method getDeclaredMethod(String nom,
Class... types)
Obtient la méthode indiquée , publique
ou non publique, dont les arguments
correspondent aux types indiqués dans
types, et qui est déclarée dans cette
classe (Les méthodes héritées ne sont
pas pris en compte).
Constructor[] getConstructors()
Obtient tous les constructeurs publics de
cette classe. Cette méthode renvoie un
Constructor
tableau
d'objets
représentant tous les constructeurs
publics.
Constructor getConstructor(Class... types)
Obtient le constructeur public indiqué de
cette
classe,
dont
les
arguments
correspondent aux types indiqués dans
types.
Constructor[] getDeclaredConstructors()
Obtient tous les constructeurs publics et
non publics de cette classe. Cette
méthode renvoie un tableau d'objets
Constructor
représentant
tous
les
constructeurs de la classe désignée par
cet objet Class.
Constructor
getDeclaredConstructor
(Class... types)
Obtient le constructeur indiqué, public
ou non public, dont les arguments
correspondent aux types indiqués dans
types.
static Class forName(String nomClasse)
Renvoie l'objet Class qui représente la
classe désignée par nomClasse.
La classe Modifier
Les trois classes possèdent également une méthode appelée getModifiers() : elle renvoie un entier dont les bits
sont utilisés comme sémaphores pour décrire les modificateurs spécifiés, tels que public ou static. Vous pouvez
alors utiliser les méthodes statiques de la classe Modifier du paquetage java.lang.reflect pour analyser les entiers
renvoyés par getModifiers(). Par exemple, il existe des méthodes telles que isPublic(), isPrivate() ou isFinal()
pour déterminer si un constructeur ou une méthode a été déclarée public, private ou final.
Il vous suffit d'appeler la méthode appropriée de Modifier et de l'utiliser sur l'entier renvoyé par getModifiers(). Il
est également possible d'employer Modifier.toString() pour afficher l'ensemble des modificateurs.
1. Les méthodes getFields(), getMethods() et getConstructors() de la classe Class renvoient dans des tableaux les attributs publics, les méthodes et les
constructeurs gérés par la classe. Ces éléments sont des objets de la classe correspondante de java.lang.reflect. Cela inclut les membres publics des
superclasses.
2. Les méthodes getDeclaredFields(), getDeclaredMethods() et getDeclaredConstructors() de Class renvoient des tableaux constitués de tous les attributs, méthodes
et constructeurs déclarés dans la classe, y compris les membres privés et protégés, mais pas les membres des superclasses.
import java.util.*;
import java.lang.reflect.*;
import static java.lang.System.*;
public class Reflexion {
public static void main(String[] args) throws Exception {
Scanner clavier = new Scanner(in);
out.println("Nom de la classe : ");
String nomClasse = clavier.next();
Class classe = Class.forName(nomClasse);
// affiche la superclasse
out.println("Classe de base --------------------------------");
out.println(" "+classe.getSuperclass());
// affiche tous les attributs
out.println("Attributs -------------------------------------");
for (Field attribut : classe.getDeclaredFields()) {
out.print(" "+Modifier.toString(attribut.getModifiers()));
out.println(" "+attribut.getType()+" "+attribut.getName()+";");
}
// affiche tous les consructeurs
out.println("Constructeurs ---------------------------------");
for (Constructor constructeur : classe.getDeclaredConstructors()) {
out.print(" "+Modifier.toString(constructeur.getModifiers()));
out.print(" "+constructeur.getName()+"(");
Class[] typeParamètres = constructeur.getParameterTypes();
for (int i=0; i<typeParamètres.length; i++) {
if (i>0) out.print(", ");
out.print(typeParamètres[i].getName());
}
out.println(");");
}
// affiche toutes les méthodes
out.println("Méthodes --------------------------------------");
for (Method méthode : classe.getDeclaredMethods()) {
out.print(" "+méthode.getReturnType()+" ");
out.print(Modifier.toString(méthode.getModifiers()));
out.print(" "+méthode.getName()+"(");
Class[] typeParamètres = méthode.getParameterTypes();
for (int i=0; i<typeParamètres.length; i++) {
if (i>0) out.print(", ");
out.print(typeParamètres[i].getName());
}
out.println(");");
}
}
}
Résultats
init:
deps-jar:
Compiling 1 source file to L:\BTS IRIS\TP
Java\reflexion\build\classes
compile:
run:
Nom de la classe :
java.awt.Point
Classe de base -------------------------------class java.awt.geom.Point2D
Attributs ------------------------------------public int x;
public int y;
private static final long serialVersionUID;
Constructeurs --------------------------------public java.awt.Point(int, int);
public java.awt.Point();
public java.awt.Point(java.awt.Point);
Méthodes -------------------------------------boolean public equals(java.lang.Object);
class java.lang.String public toString();
class java.awt.Point public getLocation();
double public getX();
double public getY();
void public move(int, int);
void public setLocation(double, double);
void public setLocation(int, int);
void public setLocation(java.awt.Point);
void public translate(int, int);
BUILD SUCCESSFUL (total time: 2 seconds)
La réflexion pour l'analyse des objets à l'exécution
Dans la section précédente, nous avons vu comment trouver le nom et le type des attributs de n'importe quel objet en
suivant la procédure suivante :
1. Obtenir l'objet Class correspondant ;
2. Appeler getDeclaredFields() sur l'objet Class.
Dans cette section, nous allons franchir une étape supplémentaire et étudier le contenu des attributs.
Accès aux champs
Bien entendu, il est facile de lire le contenu d'un champ spécifique d'un objet dont le nom et le type sont connus
lors de l'écriture du programme. Mais la réflexion permet de lire les attributs des objets qui n'étaient pas connus
au moment de la compilation.
Récupération des valeurs des attributs par la méthode get()
A cet égard, la méthode essentielle est la méthode get() de la classe Field. Si attribut est un objet de type Field
obtenu au moyen de la méthode getDeclaredFields() et moi un objet de la classe dont attribut est un attribut,
alors attribut.get(moi) renvoie un objet dont la valeur est la valeur courante de l'attribut de l'objet moi :
package test;
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) throws Exception {
Personne moi = new Personne("REMY", "Emmanuel", 46);
Class classe = moi.getClass();
Field attribut = classe.getDeclaredField("nom");
Object valeur = attribut.get(moi);
System.out.println(valeur);
}
}
class Personne {
private String nom, prénom;
private int âge;
public Personne(String nom, String prénom, int âge) {
this.nom = nom;
this.prénom = prénom;
this.âge = âge;
}
}
// Résultat : Exception de type IllegalAccessException
En réalité, ce code pose un problème. Comme l'attribut moi est privé, la méthode get() déclenche une exception
IllegalAccessException. Cette méthode ne peut être employée que pour obtenir les valeurs des champs
accessibles. Le mécanisme de sécurité Java vous permet de connaître les champs d'un objet, mais pas de lire la
valeur de ces champs si vous n'avez pas une autorisation d'accès.
Accessibilité des attributs
Par défaut, le mécanisme de réflexion respecte le contrôle des accès. Néanmoins, si un programme Java n'est pas
contrôlé par un gestionnaire de sécurité qui le lui interdit, il peut outrepasser son droit d'accès. Pour cela, il faut
invoquer la méthode setAccessible() d'un objet Field, Method ou Constructor :
attribut.setAccessible(true) ;
La méthode setAccessible() se trouve dans la classe AccessibleObject, qui est la superclasse commune des
classes Field, Method ou Constructor. Cette fonctionnalité est destinée au débogage, au stockage permanent et à
des mécanismes similaires.
package test;
Sécurité
Les accès à l'API de reflexion sont contrôlés
par un gestionnaire de sécurité. Une
application complètement sécurisée a accès
à
toutes
les
fonctionnalités
vues
précédemment ; elle peut accéder aux
membres de classes au niveau de restriction
accordé au code compris dans sa portée. Il
est toutefois possible de fournir des accès
privilégiés au code, afin qu'il puisse utiliser
l'API de réflexion et accéder à des
memebres protégés et privés d'autres
classes, ce qui est normalement interdit par
le langage Java.
Les classes Field, Method et Constructor sont
toutes des extensions de la classe de base
AccessibleObject. La classe AccessibleObject
possède
une
méthode
fondamentale,
appelée setAccessible(), qui permet de
désactiver la sécurité d'accès. à un membre
particulier d'une classe. cela semble trop
facile. C'est effectivement facile, mais le fait
que cette méthode vous permettent ou pas
de
désactiver
la
sécurité
est
une
fonctionnalité du gestionnaire de sécurité
Java et des règles associées. Vous pouvez le
faire uniquement dans une application Java
qui fonctionne sans aucune règle de
sécurité.
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) throws Exception {
Personne moi = new Personne("REMY", "Emmanuel", 46);
Class classe = moi.getClass();
Field attribut = classe.getDeclaredField("nom");
attribut.setAccessible(true);
Object valeur = attribut.get(moi);
System.out.println(valeur);
}
}
class Personne {
private String nom, prénom;
private int âge;
public Personne(String nom, String prénom, int âge) {
this.nom = nom;
this.prénom = prénom;
this.âge = âge;
}
}
// Résultat : REMY
Récupération des valeurs de type primitif
La méthode get() pose un second problème. Dans notre exemple, l'attribut nom est de type String, il est donc
possible de récupérer la valeur en tant que Object. Mais supposons que nous désirions étudier l'attribut âge.
Celui-ci est de type primitif int, et les nombres ne sont pas des objets en Java. Il existe deux solutions :
1. La première consiste à utiliser la méthode getInt() de la classe Field ;
2. la seconde est un appel à get(), car le mécanisme de réflexion enveloppe automatiquement la valeur du champ
dans la classe enveloppe appropriée, en l'occurence Integer.
L'exemple ci-dessous permet d'exploiter ces deux possibilités, ou même de travailler avec la classe de base
Object :
package test;
import java.lang.reflect.*;
public class Main {
public static void main(String[] args) throws Exception {
Personne moi = new Personne("REMY", "Emmanuel", 46);
Class classe = moi.getClass();
Field attribut = classe.getDeclaredField("âge");
attribut.setAccessible(true);
Object objet = attribut.get(moi); // première solution
System.out.println(objet);
int entier = (Integer) attribut.get(moi); // deuxième solution
System.out.println(entier);
int valeur = attribut.getInt(moi); // troisième solution
Récupération des valeurs primitives
La classe java.lang.reflect.Field représente
les attributs des objets. Field possède un jeu
complet de méthode d'accès surchargées
pour tous les types de base, par exemple
getInt() et setInt(), getBoolean() et
setBoolean() et des méthodes get() et set()
pour accéder à des attributs qui font
références à des objets.
import java.lang.reflect.*;
import static java.lang.System.*;
class Banque {
public int balance = 25;
}
public class Main {
public static void main(String[] args)
throws Exception {
Banque compte = new CompteBancaire
();
Field at = Banque.class.getField
("balance");
int balance = at.getInt(compte);
out.println("Balance = "+balance);
at.setInt(compte, 42);
out.println("Balance = "+at.getInt
(compte));
}
}
Dans cet exemple, nous sommes supposés
déjà connaître la structure de l'objet Banque.
En règle général, nous devrions pouvoir
récupérer cette information à partir de
l'objet même.
Toutes les données des méthodes d'accès de
Field prennent une référence sur l'objet
auquel nous voulons accéder. Dans le code
ci-dessus, la méthode getField() renvoie un
objet Field représentant l'attribut balance de
la classe Banque ; cet objet Field ne fait pas
référence à un objet Banque particulier. Par
conséquent, pour lire ou modifier un objet de
type Banque spécifique, nous appelons
getInt() et setInt() avec une référence à
compte, qui est le compte précis sur lequel
nous désirons travailler.
System.out.println(valeur);
Ici, nous ne faisons rien d'autre que
ce que nous aurions pu faire avec un
code statique à la compilation.
}
}
class Personne {
private String nom, prénom;
private int âge;
public Personne(String nom, String prénom, int âge) {
this.nom = nom;
this.prénom = prénom;
this.âge = âge;
}
}
L'important, c'est que nous pouvons
accéder
à
balance
en
cours
d'exécution, dans une classe chargée
dynamiquement.
Résultat
Changement de la valeur d'un attribut
Bien entendu, il est possible de modifier des valeurs obtenues. L'appel attribut.set(objet, valeur) affecte une
nouvelle valeur à l'attribut de l'objet.
init:
deps-jar:
Compiling 1 source file to C:\netbeans5.0\travail\Test\build\classes
compile:
run:
Exemple qui visualise la valeur des attributs sur un objet créé avec le constructeur par défaut
La création d'une nouvelle instance (nouvel objet) est réalisé au travers de la méthode newInstance() de la classe
Class. Cette méthode est étudiée un peu plus loin.
import java.util.*;
import java.lang.reflect.*;
import static java.lang.System.*;
public class Reflexion {
public static void main(String[] args) throws Exception {
Scanner clavier = new Scanner(in);
out.println("Nom de la classe : ");
String nomClasse = clavier.next();
Class classe = Class.forName(nomClasse);
Object objet = classe.newInstance();
// affiche la superclasse
out.println("Classe de base --------------------------------");
out.println(" "+classe.getSuperclass());
// affiche tous les attributs
out.println("Attributs -------------------------------------");
for (Field attribut : classe.getDeclaredFields()) {
out.print(" "+Modifier.toString(attribut.getModifiers()));
out.print(" "+attribut.getType()+" "+attribut.getName
()+" = ");
attribut.setAccessible(true);
out.println(attribut.get(objet)+";");
}
// affiche tous les consructeurs
out.println("Constructeurs ---------------------------------");
for (Constructor constructeur :
classe.getDeclaredConstructors()) {
out.print(" "+Modifier.toString(constructeur.getModifiers
()));
out.print(" "+constructeur.getName()+"(");
Class[] typeParamètres =
constructeur.getParameterTypes();
for (int i=0; i<typeParamètres.length; i++) {
if (i>0) out.print(", ");
out.print(typeParamètres[i].getName());
}
out.println(");");
}
// affiche toutes les méthodes
out.println("Méthodes --------------------------------------");
for (Method méthode : classe.getDeclaredMethods()) {
out.print(" "+méthode.getReturnType()+" ");
out.print(Modifier.toString(méthode.getModifiers()));
out.print(" "+méthode.getName()+"(");
Class[] typeParamètres = méthode.getParameterTypes();
for (int i=0; i<typeParamètres.length; i++) {
if (i>0) out.print(", ");
out.print(typeParamètres[i].getName());
}
out.println(");");
}
}
}
Résultats
46
46
46
BUILD SUCCESSFUL (total time: 3 seconds)
init:
deps-jar:
Compiling 1 source file to L:\BTS IRIS\TP
Java\reflexion\build\classes
compile:
run:
Nom de la classe :
java.awt.Point
Classe de base -------------------------------class java.awt.geom.Point2D
Attributs ------------------------------------public int x = 0;
public int y = 0;
private static final long serialVersionUID =
-5276940640259749850 ;
Constructeurs --------------------------------public java.awt.Point(int, int);
public java.awt.Point();
public java.awt.Point(java.awt.Point);
Méthodes -------------------------------------boolean public equals(java.lang.Object);
class java.lang.String public toString();
class java.awt.Point public getLocation();
double public getX();
double public getY();
void public move(int, int);
void public setLocation(double, double);
void public setLocation(int, int);
void public setLocation(java.awt.Point);
void public translate(int, int);
BUILD SUCCESSFUL (total time: 3 seconds)
Les pointeurs de méthodes
Les pointeurs de méthodes permettent de fournir l'adresse d'une méthode à une autre méthode, afin que la seconde
puisse appeler la première. Pour voir à l'oeuvre les pointeurs de méthodes, rappelez-vous que vous pouvez inspecter un
attribut à l'aide de la méthode get() de la classe Field. Pour sa part, la classe Method dispose d'une méthode invoke()
permettant d'appeler la méthode enveloppée dans l'objet Method. La signature de la méthode invoke() est la suivante :
Accès aux méthodes
Object Method.invoke(Object obj, Object... args) ;
Le premier paramètre est implicite, il s'agit de l'objet (si il est créé) qui possède cette méthode. Les autres objets
fournissent les éventuels paramètres explicites de la méthode invoquée.
Dans le cas d'une méthode statique, le premier paramètre est ignoré, il peut recevoir la valeur null.
.
Voici un exemple qui permet de lancer la méthode getNom() (sans arguments) de la classe Personne :
package test;
import java.lang.reflect.*;
La
classe
java.lang.reflect.Method
représente une méthode statique (de classe)
ou une simple méthode issue d'un objet.
public class Main {
public static void main(String[] args) throws Exception {
Personne moi = new Personne("REMY", "Emmanuel", 46);
Class classe = moi.getClass();
Method nom = classe.getDeclaredMethod("getNom");
System.out.println("Nom : "+nom.invoke(moi));
int âge = (Integer) classe.getDeclaredMethod("getAge").invoke(moi);
init:
deps-jar:
Compiling 1 source file to L:\BTS IRIS\TP
Java\test\build\classes
compile:
Résultats
System.out.println("Age = "+âge);
run:
}
}
Nom : REMY
Age = 46
class Personne {
private String nom, prénom;
private int âge;
public Personne(String nom, String prénom, int âge) {
this.nom = nom;
this.prénom = prénom;
this.âge = âge;
}
public String getNom() { return nom; }
public String getPrénom() { return prénom; }
public int getAge() { return âge; }
}
BUILD SUCCESSFUL (total time: 0 seconds)
A l'image des méthodes get() et set() de l'attribut Field, un problème se pose si le paramètre ou le type de résultat est non pas une classe, mais un type primitif. Il
faut se reposer encore une fois sur l'autoboxing.
Inversement si le type de retour est primitif, la méthode invoke() renverra plutôt le type enveloppe. Il faudra donc transtyper en conséquence comme cela vous est
montré dans l'exemple ci-dessus.
Obtention d'un objet de type Method
Comment obtenir un objet Method ? Il est bien évidemment possible d'appeler getDeclaredMethods(), qui renvoie un tableau d'objets Method dans lequel nous
rechercherons ensuite la méthode désirée. Nous pouvons également, comme dans l'exemple ci-dessus, appeler la méthode getDeclaredMethod(), ou getMethod(),
de la classe Class. Elle est comparable à getField(), qui reçoit une chaîne de caractères représentant un nom d'attribut et renvoie un objet Field.
Cependant, puisqu'il peut exister plusieurs méthodes homonymes, il faut être certain d'obtenir la bonne. C'est la raison pour laquelle, il faut également fournir les
types de paramètres de la méthode désirée. La signature de getMethod(), ou getDeclaredMethod(), est :
Method Class.getMethod(String nom, Class... typeParamètres) ;
Maintenant que nous connaissons les règles d'utilisation des objets Method, nous allons mettre en oeuvre un tout
petit programme qui permet d'exécuter la fonction mathématique décidée par l'opérateur :
package test;
import java.lang.reflect.*;
import java.util.Scanner;
import static java.lang.System.*;
public class Main {
public static void main(String[] args) throws Exception {
Scanner clavier = new Scanner(in);
out.println("Nom de la méthode ? ");
String nomMéthode = clavier.next();
out.println("x ? ");
double x = clavier.nextDouble();
Method méthode = Math.class.getMethod(nomMéthode, double.class);
out.println(nomMéthode+"("+x+") = "+méthode.invoke(null, x));
}
}
Résultats
init:
deps-jar:
Compiling 1 source file to C:\netbeans5.0\travail\Test\build\classes
compile:
run:
Nom de la méthode ?
cos
x?
0
cos(0.0) = 1.0
BUILD SUCCESSFUL (total time: 26 seconds)
Les paramètres et le résultat de la méthode invoke() sont nécessairement de type Object. Cela signifie que nous devons effectuer un bon nombre de transtypages.
En conséquence, le compilateur n'a pas l'occasion de vérifier votre code, et les erreurs n'apparaissent que durant les tests, lorsqu'elles sont plus difficiles à corriger.
Créer un objet à la volée au moyen de la méthode newInstance()
Comme nous l'avons déjà découvert au travers d'un exemple, il existe une autre méthode utile qui permet de créer une instance de classe à la volée. Cette méthode se
nomme, assez naturellement newInstance(). Par exemple :
classe.getClass().newInstance();
Crée une nouvelle instance (objet) du même type de classe que classe. La méthode newInstance() appelle le constructeur par défaut pour initialiser l'objet
nouvellement créé. Une exception est déclenchée si la classe ne dispose pas de constructeur par défaut.
Une combinaison de forName() et de newInstance() permet de créer un objet à partir d'un nom de classe stockée dans une chaîne :
Résultat
package test;
import java.util.Scanner;
import static java.lang.System.*;
public class Main {
public static void main(String[] args) throws Exception {
Scanner clavier = new Scanner(in);
out.print("Nom de la classe : ");
String nomClasse = clavier.next();
Object objet = Class.forName(nomClasse).newInstance();
out.println(objet);
}
}
init:
deps-jar:
Compiling 1 source file to C:\netbeans5.0\travail\Test\build\classes
compile:
run:
Nom de la classe : java.awt.Point
java.awt.Point[x=0,y=0]
BUILD SUCCESSFUL (total time: 3 seconds)
Vous ne pourrez pas employer cette technique si vous devez fournir des paramètres au constructeur d'une classe que vous souhaitez instancier à la volée. Vous
devrez alors recourir à la méthode newInstance() de la classe Constructor.
Accès aux constructeurs
La classe java.lang.reflect.Conctructor représente un constructeur d'objet qui accepte des arguments. Vous pouvez
l'utiliser, selon le gestionnaire de sécurité, pour créer une nouvelle instance d'un objet. Pour cela, il faut donc appeler la
méthode newInstance() de la classe Constructor en précisant les arguments voulus.
Object Constructor.newInstance(Object... arguments) ;
Au préalable, pour pouvoir utiliser cette méthode, il est bien sûr nécessaire de créer un objet Constructor, et ceci
au moyen de la méthode getConstructor() de la classe Class :
Constructor Class.getConstructor(Class... types)
Vous avez ci-dessous un exemple très simple qui permet de voir comment construire un objet Point pourvu de
deux arguments en utilisant le mécanisme de la réflexion :
import java.lang.reflect.*;
import java.awt.*;
public class Main {
public static void main(String[] args) throws Exception {
Constructor constructeur = Point.class.getConstructor(int.class, int.class);
Object objet = constructeur.newInstance(20, 50);
System.out.println(objet);
}
}
Les choses se passent comme dans une invocation de méthode ; il est vrai qu'un constructeur n'est rien d'autre
qu'une méthode dotée de propriétés particulières. Au moment de la création du type Constructor, vous devez
spécifier le nombre de paramètres qui composera le constructeur en précisant leur type respectif.
Tout ceci se fait au moyen de la méthode getConstructor() de la classe Class. Une fois que vous avez obtenu
l'objet Constructor, vous pouvez créer une instance correspondante au moyen de la méthode newInstance() en
spécifiant cette fois-ci la valeur des arguments.
Résultat
init:
deps-jar:
Compiling 1 source file to C:\netbeans5.0\travail\Test\build\classes
compile:
run:
java.awt.Point[x=20,y=50]
BUILD SUCCESSFUL (total time: 3 seconds)
Réflexion et générique
Depuis la JDK 5.0, la classe Class est maintenant générique. A titre d'exemple, String.class est en fait un objet de la classe Class<String>. Le paramètre de type est utile car
il permet aux méthodes de Class<T> d'être plus spécifiques sur leur type de retour. Les méthodes suivantes profitent du paramètre de type :
T newInstance()
T cast(Object obj)
T[] getEnumsConstants()
Class<? super T> getSuperclass()
Constructor<T> getConstructor(Class... typeParamètres)
Constructor<T> getDeclaredConstructor(Class... typeParamètres)
1. La méthode newInstance() renvoie une instance de la classe, obtenue à partir du constructeur par défaut. Son type de retour peut maintenant être déclaré comme
étant T, le même que celui de la classe décrite par Class<T>. Ceci épargne un trantypage.
2. La méthode cast() renvoie l'objet donné, maintenant déclaré comme type T si son type est en fait un sous-type de T. Sinon il déclenche une exception
BadCastException.
3. La méthode getEnumConstants() renvoie null si cette classe n'est pas une classe enum ou un tableau des valeurs d'énumération, connues comme étant du type T.
4. Enfin, les méthodes getConstructor() et getDeclaredConstructor() renvoient un objet Constructor<T>. La classe Constructor a aussi été rendue générique de sorte
que sa méthode newInstance() possède le type de retour correct.
Téléchargement