3. Les classes Method et Constructor

3.1. Utilisation de Method

La classe Method, comme son nom le laisse supposer, permet de modéliser les méthodes d'une classe ou d'une interface, concrètes ou abstraites. La façon la plus simple d'obtenir une référence sur une méthode, est d'utiliser une des méthodes getMethod() de la classe Class. Dans certains cas, on peut aussi recevoir une méthode en paramètre d'un callback . La classe Method fonctionne sur le même principe que la classe Class. Elle permet d'obtenir toutes les informations sur cette méthode :
  • son modificateur de visibilité ;
  • son type de retour ;
  • ses paramètres, et leurs types ;
  • les exceptions qu'elle jette ;
  • son nom ;
  • les annotations posées sur cette méthode, et sur ses paramètres.
Elle permet également d'invoquer cette méthode sur un objet particulier, avec le bon jeu de paramètres, et de récupérer l'objet retourné.

3.2. Utilisation de Constructor

La classe Constructor modélise les constructeurs d'une classe. Dans la mesure où un constructeur n'est pas une méthode, il est logique d'avoir deux classes différentes pour modéliser ces deux notions. Cela dit, les méthodes exposées et le fonctionnement de ces deux classes sont quasiment les mêmes. On obtient une instance de Constructor par invocation de l'une des méthodes getConstructor() de la classe Class. Alors que la classe Method expose une méthode invoke() pour être invoquée par introspection, la classe Constructor expose une méthode newInstance(), qui prend en paramètre les paramètres à passer au constructeur modélisé par cet objet.

3.3. Méthodes disponibles

Les premières méthodes exposées permettent de lire le nom de la méthode, et son modificateur.
  • getName() : retourne le nom de cette méthode.
  • getModifiers() : retourne les modificateurs de cette méthode, sous forme d'un int. Cet entier peut être décodé par les méthodes de la classe Modifier, comme nous le verrons dans le paragraphe suivant.
Viennent ensuite les méthodes qui permettent d'obtenir des informations sur le fonctionnement de cette méthode : son type de retour, les types de ses paramètres et les exceptions qu'elle jette.
  • getDeclaredAnnotations() et getAnnotations(Class) : retournent toutes les annotations posées sur cette méthode, ou l'annotation dont la classe est passée en paramètre, si elle existe.
  • getDeclaringClass() : retourne l'objet Class qui correspond à la classe qui porte cette méthode.
  • getExceptionTypes() : retourne le tableau des classes d'exception jetées par cette méthode. Si cette méthode ne jette pas d'exception, alors ce tableau est vide.
  • getReturnType() : retourne la classe du type retourné. Si ce type est un type primitif Java ( int, float, etc...), alors cette classe est une classe de type primitif ( int.class, float.class, etc...). Si ce type est void, alors la classe est void.class
  • getParameterTypes() : retourne le tableau des types des paramètres définis par cette méthode. L'ordre des paramètres est bien sûr conservé. On peut accéder aux annotations sur ces paramètres par la méthode getParameterAnnotations().

3.4. Invocation d'une méthode par introspection

Comme nous l'avons déjà dit, il est possible d'invoquer une méthode par introspection, sur un objet donné, avec des paramètres. Cette technique est très utilisée, dans les frameworks web et ailleurs. Elle repose sur l'utilisation de la méthode invoke(). Commençons par construire une classe simple, dont nous allons invoquer les méthodes par introspection.

Exemple 51. Utilisation de invoke() : classe de test

public  class Marin {

    private String nom ;
   
    // constructeur vide standard
    public Marin() {
      System.out.println("Invocation du constructeur vide") ;
   }
   
    // setter standard
    public  void setNom(String nom) {
      System.out.println("Invocation de setNom()") ;
       this.nom = nom ;
   }
   
    // getter standard
    public String getNom() {
      System.out.println("Invocation de setNom()") ;
       return  this.nom ;
   }
   
    // méthode toString() standard
    public String toString() {
       // utilisation d'un StringBuffer
      StringBuffer sb =  new StringBuffer() ;
      sb.append(nom) ;
      
       return sb.toString() ;
   }
}

Nous avons juste ajouté l'affichage de messages dans le getter et dans le setter , ainsi que dans le constructeur vide. Cela va nous permettre de tracer les opérations d'invocation par introspection que nous allons faire. Utilisons maintenant ce code.

Exemple 52. Utilisation de invoke() : invocation des méthodes

// dans une méthode main
 // définition de la classe à utiliser
String className =  "org.paumard.model.Marin" ;
 // définition de la propriété utilisée
String propertyName =  "nom" ;
 // valeur de cette propriété
String value =  "Surcouf" ;

 // jette une ClassNotFoundException
 // on utilise clazz car une variable ne peut pas s'appeler class
 // création de l'objet Class à partir du nom complet de cette classe
Class<?> clazz = Class.forName(className) ;

 // jette IllegalAccessException et InstantiationException
 // instanciation de cette classe
 // invoque le constructeur vide, qui doit exister
Object o = clazz.newInstance() ;

 // construction du nom du setter, on utilise la définition classique d'un setter
StringBuilder sb =  new StringBuilder() ;
sb.append("set")
  .append(propertyName.substring(0,  1).toUpperCase())
  .append(propertyName.substring(1)) ;
String setterName = sb.toString() ;
 // interrogation de la classe pour récupérer la bonne méthode
 // jette NoSuchMethodException
Method setter = clazz.getMethod(setterName, value.getClass()) ;
 // invocation de la méthode
 // jette InvocationTargetException
setter.invoke(o, value) ;

 // affichage de l'objet, invocation de sa méthode toString()
System.out.println(o) ;

 // construction du nom du getter
sb =  new StringBuilder() ;
sb.append("get")
  .append(propertyName.substring(0,  1).toUpperCase())
  .append(propertyName.substring(1)) ;
String getterName = sb.toString() ;
 // interrogation de la classe pour récupérer la bonne méthode
Method getter = clazz.getMethod(getterName) ;

 // invocation de la méthode, récupération de l'objet retourné
Object returnedValue = getter.invoke(o) ;

 // affichage de la valeur retournée
System.out.println(returnedValue) ;

L'exécution de ce code affiche les lignes suivantes.
 Invocation du constructeur vide
 Invocation de setNom()
 Surcouf
 Invocation de getNom()
 Surcouf
Les messages successifs qui s'affichent nous montrent bien que les méthodes de la classe Marin on bien été appelées par introspection. Il est donc possible, en Java, de fixer la valeur des propriétés d'un objet, ou de les lire, à partir du nom de la classe de cet objet, et du nom de ses propriétés. Ces noms sont des chaînes de caractères, que l'on peut lire dans des fichiers de configuration, ou des descripteurs XML. Cette propriété du langage est massivement utilisée.
Java API avancées
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
API Collection
1. Introduction
2. Interface Collection
2.1. Notion de Collection
2.2. Détail des méthodes disponibles
2.3. Interface Iterator
2.4. Implémentation, exemples d'utilisation
3. Interface List
3.1. Notion de List
3.2. Détail des méthodes disponibles
3.3. Interface ListIterator
3.4. Implémentations, exemples d'utilisation
4. Interface Set
4.1. Notion de Set
4.2. Implémentations HashSet et LinkedHashSet
4.3. Exemples d'utilisation
5. Interface SortedSet
5.1. Notion de SortedSet
5.2. Détails des méthodes disponibles
5.3. Exemples d'utilisation
6. Interface NavigableSet
6.1. Notion de NavigableSet
6.2. Détails des méthodes disponibles
6.3. Exemple d'utilisation
7. Interfaces Queue et Deque
7.1. Notion de file d'attente
7.2. Détail des méthodes disponibles
7.3. Utilisation des interfaces Queue et Deque
8. Tables de hachage
8.1. Notion de table de hachage
8.2. Interface Map
8.3. Interface Map.Entry
8.4. Interface SortedMap
8.5. Interface NavigableMap
8.6. Implémentations
8.7. Exemples d'utilisation
9. Classes utilitaires Collections et Arrays
9.1. Introduction
9.2. Classe Arrays
9.3. Classe Collections
Génériques
1. Introduction
2. Un premier exemple
2.1. Une première classe générique
2.2. Une première méthode générique
3. Contraindre un type générique
3.1. Problème posé
3.2. Contraindre un type générique
4. Implémentation des génériques
4.1. Type erasure
4.2. Types génériques et casts
4.3. Type générique et exception
4.4. Construction d'une instance générique
4.5. Génériques et membres statiques
4.6. Collisions de méthodes génériques
4.7. Implémentation de plusieurs types identiques
5. Type <?>
5.1. Introduction
5.2. Type ? extension d'un type
5.3. Type ? super-type d'un type
Expressions régulières
1. Introduction
2. Mise en œuvre des expressions régulières
2.1. Fonctionnement d'une regexp
2.2. Fonctionnement de l'API en Java
2.3. Un premier exemple
2.4. Classe Pattern
2.5. Classe Matcher
2.6. Utilisation des méthode find() et group()
2.7. Méthodes de remplacement
2.8. Sélection de régions
3. Syntaxe des expressions régulières
3.1. Notion de classe
3.2. Étude d'un cas réel
3.3. Recherche d'un mot précis
3.4. Recherche de deux mots précis
3.5. Recherche d'un mot commençant par une lettre donnée
3.6. Cas de mots comportant des caractères accentués
3.7. Recherche sur les lignes
Introspection
1. Introduction
2. La classe Class
2.1. Utilisation de Class
2.2. Méthodes disponibles
2.3. Remarque sur la propriété Accessible
2.4. Type d'une classe
2.5. Création d'une instance à partir d'un objet Class
2.6. Cas des énumérations
3. Les classes Method et Constructor
3.1. Utilisation de Method
3.2. Utilisation de Constructor
3.3. Méthodes disponibles
3.4. Invocation d'une méthode par introspection
4. La classe Field
4.1. Utilisation de Field
4.2. Méthodes disponibles
4.3. Accès à un champ par introspection
5. La classe Modifier
Programmation concurrente
1. Introduction
2. Lançons nos premiers threads
2.1. Introduction
2.2. Un premier thread, extension de Thread
2.3. Un deuxième thread, implémentation de Runnable
2.4. Remarque sur la méthode Thread.sleep(long)
2.5. Arrêter un thread
3. Concurrence d'accès
3.1. Notion d'état
3.2. Exemple de concurrence d'accès sur un état
3.3. Analyse de la concurrence d'accès
3.4. Solution au problème
3.5. Champs volatile
4. Synchronisation
4.1. Définition d'un bloc synchronisé
4.2. Fonctionnement d'un bloc synchronisé
4.3. Notion de deadlock
4.4. Bonnes pratiques pour la synchronisation de threads
5. Opérations atomiques
5.1. Atomicité d'une opération
5.2. Solutions disponibles
5.3. Variables atomiques
6. Collections synchronisées et concurrentes
6.1. Introduction
6.2. Position du problème
6.3. Solutions proposées
7. Files d'attente
7.1. Introduction, pattern producteur / consommateur
7.2. Interface BlockingQueue<E>
7.3. Implémentations de BlockingQueue
7.4. Exemple de producteur / consommateur
7.5. Arrêter un producteur / consommateur : pilule empoisonnée
8. Classes utilitaires de l'API Concurrent
8.1. Introduction
8.2. Énumération TimeUnit
8.3. Interface Callable<V>
8.4. Interfaces Future<V> et RunnableFuture<V>
8.5. Interface ScheduledFuture<V> et RunnableScheduledFuture<V>
9. Pattern executor
9.1. Notion de réserve de threads
9.2. Interface Executor
9.3. Interface ExecutorService
9.4. Interface ScheduledExecutorService
9.5. Classe Executors
9.6. Pattern de lancement de tâches
10. Classes de contrôle d'accès
10.1. Introduction
10.2. Interfaces Lock et ReadWriteLock
10.3. Notion de verrou réentrant
10.4. Classe RentrantLock
10.5. Classe ReadWriteRentrantLock
11. Sémaphores, barrières et latches
11.1. Introduction
11.2. Notion de sémaphore, classe Semaphore
11.3. Notion de latch, classe CountDownLatch
11.4. Notion de barrière, classe CyclicBarrier