Penser en Java 2nde édition | - | Sommaire | Préface | Avant-propos | Chapitre : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Annexe : A B C D | Tables des matières | - | Thinking in Java |
Bien sûr, cet exemple est imaginaire - vous utiliseriez probablement un attribut de classe (static) pour chaque type que vous incrémenteriez dans le constructeur pour mettre à jour les compteurs. Vous feriez cela si vous avez accès au code source de ces classes et pouvez le modifier. Comme ce n'est pas toujours le cas, le RTTI est bien pratique.
Il est intéressant de voir comment l'exemple précédent PetCount.java peut être réécrit en utilisant les littéraux de classe. Le résultat est plus satisfaisant sur bien des points :
Ici, le tableau typenames a été enlevé, on préfère obtenir de l'objet Class les chaînes identifiant les types. Notons que ce système permet au besoin de différencier classes et interfaces.
On peut aussi remarquer que la création de petTypes ne nécessite pas l'utilisation d'un block try puisqu'il est évalué à la compilation et ne lancera donc aucune exception, contrairement à Class.forName().
Quand les objets Pet sont créés dynamiquement, vous pouvez voir que le nombre aléatoire généré est compris entre un (ndt inclus) et petTypes.length (ndt exclus), donc ne peut pas prendre la valeur zéro. C'est parce que zéro réfère à Pet.class, et que nous supposons que créer un objet générique Pet n'est pas intéressant. Cependant, comme Pet.class fait partie de petTypes, le nombre total d'animaux familiers est compté.
La méthode isInstance de Class fournit un moyen d'appeler dynamiquement l'opérateur instanceof. Ainsi, toutes ces ennuyeuses expressions instanceof peuvent être supprimées de l'exemple PetCount :
On peut noter que l'utilisation de la méthode isInstance() a permis d'éliminer les expressions instanceof. De plus, cela signifie que de nouveaux types d'animaux familiers peuvent être ajoutés simplement en modifiant le tableau petTypes ; le reste du programme reste inchangé (ce qui n'est pas le cas lorsqu'on utilise des instanceof).
Lorsque vous demandez une information de type, il y a une différence importante entre l'utilisation d'une forme de instanceof (instanceof ou isInstance(), qui produisent des résultats équivalents) et la comparaison directe des objets Class. Voici un exemple qui illustre cette différence :
La méthode test() effectue une vérification du type de son argument en utilisant les deux formes de instanceof. Elle récupère ensuite la référence sur l'objet Class et utilise == et equals() pour tester l'égalité entre les objets Class. Le résultat est le suivant :
Il est rassurant de constater que instanceof et isIntance() produisent des résultats identiques, de même que equals() et ==. Mais les tests eux-mêmes aboutissent à des conclusions différentes. instanceof teste le concept de type et signifie « es-tu de cette classe, ou d'une classe dérivée ? ». Autrement, si on compare les objets Class en utilisant ==, il n'est plus question d'héritage - l'objet est de ce type ou non.
Java effectue son identification dynamique de type (RTTI) à l'aide de l'objet Class, même lors d'un transtypage. La classe Class dispose aussi de nombreuses autres manières d'être utilisée pour le RTTI.
Premièrement, il faut obtenir une référence sur l'objet Class approprié. Une manière de le faire, comme nous l'avons vu dans l'exemple précédent, est d'utiliser une chaîne de caractères et la méthode Class.forName(). C'est très pratique car il n'est pas nécessaire d'avoir un objet de ce type pour obtenir la référence sur l'objet Class. Néanmoins, si vous avez déjà un objet de ce type, vous pouvez retrouver la référence à l'objet Class en appelant une méthode qui appartient à la classe racine Object : getClass(). Elle retourne une référence sur l'objet Class représentant le type actuel de l'objet. Class a de nombreuses méthodes intéressantes, comme le montre l'exemple suivant :
On peut voir que la classe FancyToy est assez compliquée, puisqu'elle hérite de Toy et implémente les interfaces HasBatteries, Waterproof et ShootThings. Dans main(), une référence de Class est créée et initialisée pour la classe FancyToy en utilisant forName() à l'intérieur du block try approprié.
La méthode Class.getInterfaces() retourne un tableau d'objets Class représentant les interfaces qui sont contenues dans l'objet en question.
Si vous avez un objet Class, vous pouvez aussi lui demander la classe dont il hérite directement en utilisant la méthode getSuperclass(). Celle-ci retourne, bien sûr, une référence de Class que vous pouvez interroger plus en détail. Cela signifie qu'à l'éxécution, vous pouvez découvrir la hiérarchie de classe complète d'un objet.
La méthode newInstance() de Class peut, au premier abord, ressembler à un autre moyen de cloner() un objet. Néanmoins, vous pouvez créer un nouvel objet avec newInstance() sans un objet existant, comme nous le voyons ici, car il n'y a pas d'objets Toy - seulement cy qui est une référence sur l'objet Class de y. C'est un moyen de construire un « constructeur virtuel », qui vous permet d'exprimer « je ne sais pas exactement de quel type vous êtes, mais créez-vous proprement ». Dans l'exemple ci-dessus, cy est seulement une référence sur Class sans aucune autre information à la compilation. Et lorsque vous créez une nouvelle instance, vous obtenez une référence sur un Object. Mais cette référence pointe sur un objet Toy. Bien entendu, avant de pouvoir envoyer d'autres messages que ceux acceptés par Object, vous devez l'examiner un peu plus et effectuer quelques transtypages. De plus, la classe de l'objet créé par newInstance() doit avoir un constructeur par défaut. Dans la prochaine section, nous verrons comment créer dynamiquement des objets de classes utilisant n'importe quel constructeur, avec l'API de réflexion Java.
La dernière méthode dans le listing est printInfo(), qui prend en paramètre une référence sur Class, récupère son nom avec getName(), et détermine si c'est une interface avec isInterface().
Le résultat de ce programme est :
Ainsi, avec l'objet Class, vous pouvez découvrir vraiment tout ce que vous voulez savoir sur un objet.
Si vous ne connaissez pas le type précis d'un objet, le RTTI vous le dira. Néanmoins, il y a une limitation : le type doit être connu à la compilation afin que vous puissiez le détecter en utilisant le RTTI et faire quelque chose d'intéressant avec cette information. Autrement dit, le compilateur doit connaître toutes les classes que vous utilisez pour le RTTI.
Ceci peut ne pas paraître une grande limitation à première vue, mais supposons que l'on vous donne une référence sur un objet qui n'est pas dans l'espace de votre programme. En fait, la classe de l'objet n'est même pas disponible lors de la compilation. Par exemple, supposons que vous récupériez un paquet d'octets à partir d'un fichier sur disque ou via une connexion réseau et que l'on vous dise que ces octets représentent une classe. Puisque le compilateur ne peut pas connaître la classe lorsqu'il compile le code, comment pouvez vous utilisez cette classe ?
Dans un environnement de travail traditionnel cela peut sembler un scénario improbable. Mais dès que l'on se déplace dans un monde de la programmation plus vaste, il y a des cas importants dans lesquels cela arrive. Le premier est la programmation par composants, dans lequel vous construisez vos projets en utilisant le Rapid Application Development (RAD) dans un constructeur d'application. C'est une approche visuelle pour créer un programme (que vous voyez à l'écran comme un « formulaire » (form)) en déplaçant des icônes qui représentent des composants dans le formulaire. Ces composants sont alors configurés en fixant certaines de leurs valeurs. Cette configuration durant la conception nécessite que chacun des composants soit instanciable, qu'il dévoile une partie de lui-même et qu'il permette que ses valeurs soient lues et fixées. De plus, les composants qui gèrent des événements dans une GUI doivent dévoiler des informations à propos des méthodes appropriées pour que l'environnement RAD puisse aider le programmeur à redéfinir ces méthodes de gestion d'événements. La réflexion fournit le mécanisme pour détecter les méthodes disponibles et produire leurs noms. Java fournit une structure de programmation par composants au travers de JavaBeans (décrit dans le chapitre 13).