faudrait au minimum que les méthodes applicables soient déclarées dans une interface
I
, laquelle serait le
type dé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 typé 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 déclaration de la méthode abstraite
est exécuté par la mé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 où
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