FAQ Langage JavaConsultez toutes les FAQ

Nombre d'auteurs : 41, nombre de questions : 294, dernière mise à jour : 21 mars 2016  Ajouter une question

 

Cette FAQ a été réalisée à partir des questions fréquemment posées sur le forum Java de http://java.developpez.com ainsi que l'expérience personnelle des auteurs.

Nous tenons à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes. Les auteurs font leur maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur, ou que vous souhaitez nous aider en devenant rédacteur, lisez ceci.

Sur ce, nous vous souhaitons une bonne lecture.


SommaireRéflexivité (16)
précédent sommaire suivant
 

L'introspection consiste en la découverte dynamique des informations propres à une classe Java ou à un objet. Ce mécanisme est notamment utilisé au niveau de la machine virtuelle Java lors de l'exécution de votre programme, et donne lieu à une API.

Le paquetage java.lang.reflect permet l'introspection en rendant possible l'accès aux classes, à leurs champs, méthodes ou encore constructeurs, et à toutes les informations les caractérisant, même celles qu'on pensait inaccessibles. Elle est également très utile pour instancier des classes de manière dynamique, dans le processus de sérialisation d'un bean Java, ainsi que dans la génération de code. L'introspection est souvent utilisée dans les bibliothèques dédiées à l'injection de dépendances ou de création de bouchons.

Les métadonnées présentes dans les fichiers binaires renseignent sur le contenu de chaque classe répertoriée et permettent à la JVM de procéder à des vérifications lors de l'exécution d'un programme (pensez à l'exception java.lang.NoSuchMethodError. La JVM utilise également ces informations pour vous proposer la complétion de code dans les environnements de développement Java, ce qui se fait en temps réel (pas de génération à faire au préalable).

Des détails sur le format des métadonnées dans un fichier binaire sont disponibles dans les documents de spécification de la machine virtuelle Java. Sachez seulement que les champs et méthodes sont identifiés par leur nom, le type (identifiants spécifiques à la JVM) pour les champs, et la signature pour les méthodes. À partir de ces informations, la JVM sait directement localiser la portion de byte code correspondant à l'implémentation d'une méthode.

Mis à jour le 19 août 2015 Mickael Baron Ricky81

Considérons les classes suivantes :

  1. La première classe appelée PremiereClasse n'hérite d'aucune classe (hormis celle par défaut qui est Object)
    Code Java : Sélectionner tout
    1
    2
    3
    4
    5
    package monpackage; 
      
    public class PremiereClasse { 
        [...] 
    }
  2. La deuxième classe appelée DeuxiemeClasse hérite de PremiereClasse.
    Code Java : Sélectionner tout
    1
    2
    3
    4
    5
    package monpackage; 
      
    public class DeuxiemeClasse extends PremiereClasse { 
        [...] 
    }
  3. Enfin la troisième classe appelée TroisiemeClasse hérite de DeuxiemeClasse.
    Code Java : Sélectionner tout
    1
    2
    3
    4
    5
    package monpackage; 
      
    public class TroisiemeClasse extends DeuxiemeClasse { 
        [...] 
    }


On souhaite déterminer l'ensemble des classes héritées (directe et indirectes) à partir de TroisiemeClasse. Nous allons utiliser à cet effet la méthode nommée getSuperClass() de la classe java.lang.Class :

Code Java : Sélectionner tout
1
2
3
4
Class c = Class.forName("monpackage.TroisiemeClasse"); 
while ((c=c.getSuperclass()) != null) { 
    System.out.println(c.getName()); 
}

Mis à jour le 19 août 2015 Mickael Baron Ricky81

La classe java.lang.Class possède une méthode nommée getInterfaces() qui renvoie un tableau des interfaces implémentées par la classe cible.

Considérons la classe suivante MaClasse qui implémente deux interfaces PremierInterface et DeuxiemeInterface :

Code Java : Sélectionner tout
1
2
3
4
5
package monpackage; 
  
public class MaClasse implements PremiereInterface, DeuxiemeInterface { 
    [...] 
}


Le code suivant permet de lister l'ensemble des interfaces que MaClasse implémente :
Code Java : Sélectionner tout
1
2
3
4
5
Class c = Class.forName("monpackage.MaClasse"); 
Class[] interfaces = c.getInterfaces(); 
for (Class currentInterface : interfaces) { 
   System.out.println(currentInterface.getName()); 
}

Mis à jour le 19 août 2015 Mickael Baron Ricky81

La classe java.lang.Class possède une méthode nommée getPackage() qui renvoie un objet de type java.lang.Package.

Considérons la code de la classe MaClasse :

Code Java : Sélectionner tout
1
2
3
4
5
package monpackage;  
  
public class MaClasse {  
    [...] 
}


Le code pour récupérer le nom du package est le suivant :

Code Java : Sélectionner tout
1
2
3
Class c = Class.forName("monPackage.MaClasse"); 
Package p = c.getPackage(); 
System.out.println(p.getName());

Mis à jour le 19 août 2015 Mickael Baron Ricky81

La liste des méthodes publiques (même celles héritées) est disponible grâce à la méthode getMethods() des instances de type java.lang.Class.

Code Java : Sélectionner tout
1
2
Class c = Class.forName("monPackage.MaClasse"); 
java.lang.reflect.Method[] m = c.getMethods();

Il est également possible de récupérer une méthode par son nom et sa signature en utilisant la méthode getMethod(String name, Class... parameterTypes).

Mis à jour le 19 août 2015 Mickael Baron Ricky81

La classe java.lang.reflect.Method dispose d'un certain nombre de méthodes permettant de reconstituer la signature d'une méthode.

En considérant la classe suivante :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
package monpackage;   
  
public class MaClasse {   
    public String displaySomeThing(String param1, String param2, Long param3) { 
        [...] 
    } 
}


Voici un exemple de deux d'entre elles :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
Method currentMethod = ...// currentMethod est un objet qui définit la méthode displaySomeThing 
System.out.println("Nom méthode : " + method.getName()); 
System.out.println("Type de retour : " + method.getReturnType().getName()); 
final Class<?>[] parameterTypes = method.getParameterTypes(); 
System.out.println("Paramètres d'entrées : "); 
for (Class<?> class1 : parameterTypes) { 
    System.out.print(class1.getName() + " "); 
}

Mis à jour le 28 août 2015 Mickael Baron Ricky81

La classe java.lang.Class possède une méthode nommée getFields() qui permet de récupérer les champs en accès public d'une classe :

Code Java : Sélectionner tout
1
2
Class c = Class.forName("maClasse"); 
java.lang.reflect.Field[] f = c.getFields();

Il est également possible de récupérer un champ par son nom en utilisant la méthode getField(String name).

Mis à jour le 10 octobre 2015 Mickael Baron Ricky81

Ces informations peuvent être obtenues en invoquant les méthodes getName() et getType() de la classe java.lang.reflect.Field. Ces méthodes renvoient le nom et le type du champ. Il existe une autre façon d'obtenir les informations complémentaires : la méthode getModifiers(). Nous nous retrouvons avec un entier qu'il faut analyser à l'aide de la classe java.lang.reflect.Modifier. Nous allons ici utiliser la méthode statique toString() pour obtenir ces informations sous la forme d'une chaîne de caractères.

Code Java : Sélectionner tout
1
2
3
// f est un objet de type Field 
int mod = f.getModifiers(); 
System.out.println(java.lang.reflect.Modifier.toString(mod));

Avec la valeur retournée par la méthode getModifiers(), vous pouvez également vérifier via la classe utilitaire java.lang.reflect.Modifier si les informations contiennent quelque chose de spécifique, en invoquant ses méthodes statiques isXXX().

Par exemple :

Code Java : Sélectionner tout
1
2
3
4
5
6
// f est un objet de type Field 
int mod = f.getModifiers(); 
System.out.println("f est static = " + Modifier.isStatic(mod)); 
System.out.println("f est transient = " + Modifier.isTransient(mod)); 
System.out.println("f est volatile = " + Modifier.isVolatile(mod)); 
...

Mis à jour le 10 octobre 2015 bentechno Mickael Baron Ricky81

La classe java.lang.reflect.Modifier gère l'ensemble des informations définies pour un champ ou une méthode, à savoir : la visibilité, l'attribut constante ainsi que le caractère statique. Ce sont essentiellement des méthodes de classe qui permettent de consulter ces caractéristiques à partir d'un entier.

Code Java : Sélectionner tout
1
2
3
4
5
// m est un objet de type Method 
int mod = m.getModifiers(); 
if (java.lang.reflect.Modifier.isStatic(mod)) { 
    System.out.println("méthode statique"); 
}

Mis à jour le 10 octobre 2015 Mickael Baron Ricky81

Pour consulter ou modifier un champ donné d'un objet de façon dynamique, il faut commencer par récupérer l'objet de type java.lang.reflect.Field correspondant au champ en question. Il suffit ensuite d'appeler la méthode correspondante avec pour premier paramètre l'objet cible.

Prenons l'exemple suivant où nous modifions le contenu du champ défini par la variable nomChamp de l'objet obj en lui donnant la valeur définie par la variable val.

Code Java : Sélectionner tout
1
2
3
4
void changeValeur(Object obj, String nomChamp, Object val) throws Exception { 
    java.lang.reflect.Field f = obj.getClass().getField(nomChamp); 
    f.set(obj,val); 
}

Un exemple de consultation de la valeur d'un champ donné :

Code Java : Sélectionner tout
1
2
3
4
void afficheValeur(Object obj, String nomChamp) throws Exception { 
    Field f = obj.getClass().getField(nomChamp); 
    System.out.println(f.get(obj)); 
}

Remarque : les méthodes set() et get() sont des méthodes générales, mais il existe aussi des équivalents pour les types classiques : setDouble(Object obj, double d) ou setBoolean(Object obj, boolean z).

Mis à jour le 10 octobre 2015 Ricky81

Nous allons utiliser la méthode invoke() définie dans la classe java.lang.reflect.Method. Cette méthode prend comme premier paramètre l'objet sur lequel invoquer la méthode, suivi de chacun des paramètres habituels de la méthode.

Voici un exemple générique de lancement dynamique d'une méthode donnée sur un objet :

Code Java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
Object lancerMethode(Object obj, Object[] args, String nomMethode) throws Exception { 
    Class[] paramTypes = null; 
    if (args != null) { 
        paramTypes = new Class[args.length]; 
        for (int i=0;i<args.length;++i) { 
            paramTypes[i] = args[i].getClass(); 
        } 
    } 
    Method m = obj.getClass().getMethod(nomMethode, paramTypes); 
    return m.invoke(obj,args); 
}

À noter que cette méthode ne fonctionne que pour les types exacts de classe pour les paramètres. C'est-à-dire qu'on ne pourra pas utiliser une classe fille comme argument de paramètre.

Mis à jour le 10 octobre 2015 Mickael Baron Ricky81

Pour lancer dynamiquement une méthode de classe ou statique, le principe est le même que pour une méthode sur un objet. La signature de la méthode de la classe classe java.lang.reflect.Method à utiliser étant :

Code Java : Sélectionner tout
Object invoke(Object obj, Object[] args)

Lorsque la méthode est statique, le paramètre obj n'est pas évalué ; vous pouvez donc utiliser la valeur null.

Mis à jour le 10 octobre 2015 Mickael Baron Ricky81

Les types primitifs (byte, char, short, int, long, float, double et boolean) ont tous des wrappers dans le package java.lang. Respectivement: Byte, Character, Short, Integer, Long, Float, Double et Boolean.

Chacune de ces classes contient une variable statique TYPE de type Class. C'est cette classe qu'il faut utiliser pour spécifier le type du paramètre ; la valeur, quant à elle, sera contenue dans un objet du type du wrapper.

Prenons l'exemple d'un appel d'une méthode prenant un int en paramètre. Dans cet exemple nous allons appeler la méthode statique abs(int) de la classe java.lang.Math :

Code Java : Sélectionner tout
1
2
3
4
5
Class types[] = { Integer.TYPE };  
Method method = Math.class.getMethod("abs", types);  
Object parametres[] = { new Integer(-1) };  
Integer iWrap = (Integer) method.invoke(null, parametres);  
System.out.println("Valeur absolue de -1 = " + iWrap);

Mis à jour le 10 octobre 2015 bulbo Mickael Baron

Après avoir récupéré un champ privé sur un objet, vous pouvez tenter de désactiver la protection en invoquant sa méthode setAccessible() avec la valeur true. Si l'utilisation de cette méthode ne provoque pas le déclenchement d'une exception par un SecurityManager, vous allez pouvoir modifier la valeur du champ.

Par exemple, prenons une classe Secret avec un champ privé priv de type String :

Code Java : Sélectionner tout
1
2
3
public final class Secret { 
    private String priv; 
}

Nous pouvons modifier la valeur de ce champ en faisant :

Code Java : Sélectionner tout
1
2
3
4
5
void modifierChamp(Secret s, String val) { 
    Field f = s.getClass().getDeclaredField("priv"); 
    f.setAccessible(true); 
    f.set(s,val); 
}

Mis à jour le 10 octobre 2015 Mickael Baron Ricky81

Les moyens mis à disposition par l'API Reflection permettent de passer outre les règles de l'encapsulation. Vous pourrez consulter les champs et méthodes privés/protégés de la même manière que les champs et méthodes publics, mais en utilisant les méthodes getDeclaredFields() et getDeclaredMethods() (ainsi que leurs variantes) au lieu de getFields() et getMethods().

Par exemple :

Code Java : Sélectionner tout
1
2
Class c = Class.forName("maClasse"); 
java.lang.reflect.Field[] f = c.getDeclaredFields();

Remarque : contrairement aux méthodes getFields() et getMethods(), les méthodes getDeclaredFields() et getDeclaredMethods() ne renvoient pas les informations héritées. Dans ce cas, il est nécessaire d'aller interroger la classe mère.

Mis à jour le 5 octobre 2015 Mickael Baron Ricky81


Il est possible de marquer certains éléments du langage avec des Annotations, ces dernières peuvent être accessibles lors de l'exécution (seulement pour les annotations dont la rétention est RetentionPolicy.RUNTIME). Le package java.lang.reflect se voit doté d'une interface, nommée AnnotatedElement, qui décrit quatre méthodes permettant d'accéder aux annotations, on y trouve ainsi les méthodes suivantes :

  • getAnnotation(Class) qui permet d'obtenir une annotation particulière (si elle est présente) ;
  • getAnnotations() qui permet d'obtenir un tableau contenant toutes les annotations de l'élément ;
  • getDeclaredAnnotations() qui permet d'obtenir un tableau contenant toutes les annotations directement déclarées sur l'élément (en ignorant ainsi les annotations héritées de la classe parent) ;
  • isAnnotationPresent(Class) qui permet simplement de savoir si une annotation particulière est présente.


Cette interface est implémentée par les classes suivantes : Class, Package, Constructor, Method et Field.

Enfin, les classes Constructor et Method proposent également une méthode getParameterAnnotations() afin d'accéder aux annotations de leurs paramètres…

Mis à jour le 5 octobre 2015 adiGuba Mickael Baron

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2016 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

 
Responsable bénévole de la rubrique Java : Mickael Baron -