UFR 922 Informatique
Master Informatique – Spécialité STL
Module ALADYN
TP — Collections dynamiquement typées
Jacques Malenfant
professeur des universités
Résumé
L’objectif de ce TP est de comprendre les possibilités offertes par l’API Reflection de Java par la réa-
lisation de collections dynamiquement typées. L’idée est d’utiliser l’API Reflection de Java pour réaliser
une vérification de type à l’exécution à la fois lors de l’insertion des objets dans la collection et lors des
appels de méthodes d’ordre supérieur sur ces objets.
1 API Reflection de Java
Le langage Java offre depuis sa version 1.1 un ensemble de classes permettant de faire de la réflexion
structurelle. Ces classes sont la classe
java.lang.Class
ainsi que celles regroupées dans le package ap-
pelé
java.lang.reflect
. En utilisant ce package, il devient possible d’explorer le contenu d’une classe,
c’est-à-dire ses déclarations de champs, de méthodes, sa superclasse, etc. Les champs sont représentés par
des instances de la classe
java.lang.reflect.Field
, alors que les méthodes sont représentées par des
instances de la classe
java.lang.reflect.Method
. Ces classes ne fournissent pas à proprement parler
une représentation réifiée des entités correspondantes, mais elles sont suffisantes pour une introspection sur
les noms et les types utilisés, de même que pour l’application des méthodes.
2 Typage des collections en Java
La puissance d’expression des langages fonctionnels tient en bonne partie à leur capacité de program-
mation d’ordre supérieur, à savoir la capacité à l’utilisation des fonctions comme entités de plein droit
(passables en paramètre, affectables à des variables, retournables en résultat, etc.). Lexemple typique de
programmation d’ordre supérieur est la fonctionnelle
map
qui prend deux paramètres, une fonction fd’un
argument et une liste l, et applique fà chaque élément de la liste pour retourner la liste des résultats.
Une telle forme de programmation est possible en Java. Considérez une collection d’objets en Java.
Une methode
map
de signature :
Object[] map(String methodName, Object[] params)
methodName
donne le nom de la méthode à appliquer et
params
donne le tableau des arguments à
utiliser lors de l’application. L’API Reflection de Java permet ensuite d’appliquer la méthode
methodName
à chaque object de la collection via l’appel de la méthode
invoke
sur l’objet méthode correspondant à
methodName
dans la classe de chacun de ces objets. Se pose cependant le problème du typage. Dans la
signature ci-dessus, les types des arguments et du résultat sont
Object
, ce qui laisse toute latitude mais
abandonne de fait presque toute vérification des types.
En Java 1.5, le typage statique (à la compilation) deviendrait possible, en utilisant les types génériques,
mais pour ce faire il faudrait forcer toutes les classes des objets insérables dans la collection à respecter
certaines déclarations. Pour envoyer le message correspondantà
methodName
aux objets de la collection, il
faudrait au minimum que les méthodes applicables soient déclarées dans une interface
I
, laquelle serait le
type claré des objets mémorisés dans la collection. Symétriquement, tous les objets mémorisables dans
la collection devraient être instances de classes implantant ladite interface. Alors, un envoi de message
o.methodName(...)
pourrait être ty statiquement contre cette interface.
L’inconvénient vient e ce qu’il n’est pas toujours possible de modifier les classes des objets pour leur
faire déclarer explicitement une certaine interface
I
, même si toutes les classes concernées possèdent effec-
tivement des méthodes réalisant l’interface. C’est le cas par exemple de classes provenant de bibliothèques
dont on ne contrôle pas le code source.
Le principe du TP consiste à utiliser la réflexion structurelle de Java pour faire ces vérifications dynami-
quement, et ainsi éviter de forcer toutes les classes des objets à implanter l’interface, ce qui est intéressant
dans la mesure où cela permet de créer la collection a posteriori puisque les classes des objets n’ont pas à
implanter une unique interface connue au préalable.
L’idée est de considérer que sans implanter une même interface (au sens de la déclaration
implements
de Java), ou hériter d’une même classe, un ensemble de classes peuvent implanter une même méthode,
laquelle peut être utilisée dans un
map
. Si la collection possède une interface qui va déclarer toutes les
méthodes sur lesquelles peuvent porter un
map
. Lorsqu’un objet sera ajouté à la collection, on peut utiliser
l’API Reflection pour vérifier que la classe de cet objet possède bien des implantations pour toutes les mé-
thodes définies par l’interface. Enfin, on utilise la méthode
invoke
de la classe
java.lang.reflect.Method
pour appliquer la méthode sur chacun des objets.
La vérification des méthodes de la classe des objets se fera dans cette première partie en utilisant
la notion de conformité propre à Java, à savoir qu’une méthode implante la déclaration d’une méthode
abstraite dans une interface si elle possède le même nom, le même type de retour ainsi que le même nombre
et les mêmes types des paramètres formels. C’est la règle de l’invariance des types. D’autres politiques de
conformité sont possibles, comme la contravariance et la covariance.
3 Covariance et contravariance
La règle de Java pour la conformité des types entre méthodes concrètes et méthodes abstraites, ou entre
méthodes concrètes redéfinies est très stricte : l’invariance. Cela veut dire que ne sont conformes que des
méthodes ayant exactement la même signature. Pourtant, il est possible d’être moins strict sans sacrifier la
sureté des programmes. Considérez une méthode abstraite
m
dans une interface :
A m(A
1
a
1
, A
2
a
2
, ..., A
n
a
n
)
et une méthode concrète de même nom dans une classe :
C m(C
1
c
1
, C
2
c
2
, ..., C
n
c
n
)
Pour chaque paramètre i, que se passe-t-il si un appel conforme à la claration de la méthode abstraite
est exécuté par la thode concrète? Certes, si la classe
C
iest la même que la classe
A
i, tout se passe
bien. Mais si la classe
C
iest superclasse de
A
i, aucun message envoyé au paramètre
c
idans la méthode
concrète ne sera incompris du paramètre réel de type
A
i, puisque par définition tous les messages compris
d’une instance de la superclasse
C
iest aussi compris d’une instance de la sous-classe
A
i. C’est la règle de
la contravariance des types des paramètres.
Pour ce qui est des types des résultats, c’est l’inverse. Que
A
soit une superclasse de
C
, et alors tous
les messages envoyés par l’utilisateur du résultat de l’appel via la méthode abstraite seront nécessairement
par une instance de
C
, le résultat de la méthode concrète exécutée. C’est la règle de covariance du type du
résultat.
La covariance du résultat et la contravariance des paramètres donnent un typage sûr, c’est-à-dire
il ne peut se produire d’erreurs de types lors de l’appel. La contravariance des paramètres est cependant
parfois difficile à respecter. Considérez par exemple une méthode
add
sur des points. Supposez que vous
ayez la classe
Point2D
et une sous-classe
Point3D
. Il est clair que la méthode
add
sur la classe
Point2D
doit prendre en paramètre un
Point2D
et retourner un
Point2D
. Sur la classe
Point3D
, on souhaite plutôt
que le paramètre soit un
Point3D
et le résultat également un
Point3D
. Mais alors, il y a covariance du
paramètre.
Si on admet la règle de covariance des paramètres et qu’on accepte l’exemple des points précédents, on
ouvre la porte à des erreurs de type lors de l’appel. Si on a dans un programme le fragment suivant :
Point2D p1 = p2.add(new Point2D(1, 2) ;
tout se passe bien si
p2
contient un
Point2D
. Par contre, si
p2
contient un
Point3D
, ce qui est possible
par le principe de substitution des objets, une erreur de type se produit puisqu’on ne peut pas substituer
un paramètre réel
Point2D
au paramètre formel
Point3D
de la méthode
add
de la classe
Point3D
. Si on
veut détecter une telle erreur avant l’exécution de la méthode comme telle, il faut vérifier que lorsque la
méthode appelée est celle de la classe
Point3D
, le paramètre est au moins instance de la classe
Point3D
ou d’une de ses sous-classe.
4 Réalisations attendues
Vous devez créer une classe abstraite
fr.upmc.aladyn.util.DynamicCollection
permettant de mé-
moriser des objets sur lesquels il est possible d’appliquer l’opération d’ordre supérieur
map
. Cette classe est
abstraite pour deux raisons : elle ne spécifie pas la politique de conformité des types et elle ne donne pas une
représentation concrète de la collection dynamique. La classe
fr.upmc.aladyn.util.DynamicCollection
doit implanterl’interface
fr.upmc.aladyn.util.IDynamicCollection
, étendant l’interface Java
java.util.Collection
avec les méthodes suivantes :
void setType(Class)
initialise le type des éléments de la collection en passant une instance de
java.lang.Class
repré-
sentant l’interface à respecter.
Class getType()
retourne le type des éléments de la collection.
boolean conformsToType(Object)
retourne vrai si l’objet passé en paramètre est conforme au type des éléments de la collection. Elle
sera abstraite dans la classe
fr.upmc.aladyn.util.DynamicCollection
mais concrète dans les
sous-classes de cette dernière.
Object[] map(String, Object[])
applique la méthode dont le nom est donné par le premier paramètre sur chaque object de la collection
avec les paramètres donnés par le second paramètre et retourne le tableau des objets résultant; la
méthode doit exister et l’appel doit être sûr au sens du typage (c’est-à-dire qu’il doit exister une
méthode dans chaque élément dont les paramètres sont conformes aux arguments. La méthode peut
lever l’exception
TypeCheckException
lorsque la méthode appelée n’existe pas dans l’interface de
la collection dynamique, ainsi que l’exception
LatentTypeCheckException
si les arguments passés
en paramètres ne sont pas conforme à la méthode à appliquer d’au moins un des objets de la collection
dans le cas de la collection covariante.
L’implantation de la méthode
add
de l’interface
java.util.Collection
doit décharger autant que
possible la méthode
map
des vérifications de types pouvant être faites lors de l’insertion. Si un objet n’est
pas conforme au type des éléments, la méthode
add
lève l’exception
ClassCastException
.
Vous devrez ensuite créer trois sous-classes abstraites :
une classe
fr.upmc.aladyn.util.InvariantCollection
, réalisant un typage invariant,
une classe
fr.upmc.aladyn.util.ContravariantCollection
, réalisant un typage contravariant,
et
une classe
fr.upmc.aladyn.util.CovariantCollection
, réalisant un typage covariant.
À partir de ces trois classes, il doit être possible de créer des sous-classes concrètes, comme
InvariantList
ou encore
ContravariantArray
, que vous pourrez implanter pour vos tests.
5 Évaluation et modalités pour rendre le projet
Pour éviter toute mésentente, voici de manière assez précises les modalités d’évaluation et de remise
du projet. Vous devriez ne pas avoir besoin d’autant de précisions tatillonnes, donc cela ne vise que les cas
problématiques.
Modalités d’évaluation :
Le projet comptera pour 15% de la note finale de l’UE au titre du contrôle continu.
Les critères d’évaluation sont :
respect du cahier des charges (dont les noms de méthodes, de classes et de « packages », etc.) ;
lisibilité et qualité du code source produit, y compris la documentation et les commentaires (le
code source sera examiné et la note du projet comportera une partie sur la qualité de ce source en
termes de lisibilité, comme le fait de pratiquer une bonne indentation, de ne jamais dépasser 80
caractères par ligne, et de respecter les conventions standard de Java, et en termes de qualité des
solutions);
qualité et couverture des tests d’exécution qui devraont être faits en utilisant JUnit (pas d’inflation
inutile cependant, il vaut mieux réfléchir à quelques tests couvrant bien les différentes possibilités
plutôt que de produire des dizaines de tests);
exactitude des résultats d’exécution, qui sera vérifiée lors d’une démonstration à faire (sur rendez-
vous, après la remise du projet).
Les retards seront sanctionnés selon un barême linéaire dans le temps : chaque tranche de 12 heures
de retard entamée donnera lieu à un pénalité de 5 points sur 20, et donc un retard de plus de 48 heures
donnera lieu à une note de 0 à ce projet.
Modalités de remise des projets :
Le projet se fait obligatoirement en binôme. Tous les fichiers doivent comporter les noms (authors
en Javadoc) des auteurs.
Le projet est à rendre le jeudi 15 octobre 2009 à minuit au plus tard sous la forme d’une archive
tgz
si vous travaillez sous Unix ou
zip
si vous travaillez sous Windows que vous m’enverrez à
Jacques.Malen[email protected]
comme attachement fait proprement avec votre programme de ges-
tion de courrier préféré.
Votre projet utilisera JUnit pour faire des tests de vos classes. Il comportera aussi des classes concrètes
permettant de réaliser les tests. Votre documentation devra indiquer comment exécuter les tests.
1 / 4 100%
La catégorie de ce document est-elle correcte?
Merci pour votre participation!

Faire une suggestion

Avez-vous trouvé des erreurs dans linterface ou les textes ? Ou savez-vous comment améliorer linterface utilisateur de StudyLib ? Nhésitez pas à envoyer vos suggestions. Cest très important pour nous !