août
2011
Le projet Lambda, poussé par Oracle a peut-être engendrer un nouveau bébé, les « public defenders methods« , dont l’objectif est de permettre de faire évoluer les interfaces Java.
De prime abord cela n’a aucun lien avec les expressions lambdas (ou « closures »), mais l’intérêt étant de pouvoir réellement enrichir l’API avec ces dernières. En effet les interfaces étant figées, il est assez difficile de faire évoluer l’API : le simple ajout d’une nouvelle méthode entraine de nombreuses incompatibilités.
Le rapport avec les expressions lambdas ? Ces dernières sont supposées simplifier et enrichir l’API, mais sont fortement limité par l’immuabilité des interfaces. Il fallait remettre cela en cause !
« public defenders methods »
C’est là tout l’intérêt des « public defenders methods« , qui, en permettant d’ajouter de nouvelles méthodes à des interfaces existantes, permettront de pouvoir utiliser au mieux les expressions lambdas.
Mais oublions les expressions lambdas pour se concentrer sur ce nouveau concept, et prenons un exemple concret et bien connu avec l’interface java.util.List et les méthodes Collections.sort().
Actuellement pour trier une List
on utilise un code tel que celui-ci :
List<String> list = ...
Collections.sort(list);
On se retrouve avec trois principaux défauts :
- La syntaxe n’est pas très consistante avec ce que l’on a l’habitude de voir en Java. En fait on s’attendrait plutôt à avoir quelque chose comme list.sort().
- La méthode Collections.sort() n’est aucunement lié à l’interface List. Il est donc nécessaire de la connaitre afin de pouvoir l’utiliser. Impossible de trouver cette méthode si on se limite à la javadoc de l’interface List et à la complétion de l’EDI. Or il s’agit des premiers réflexes lorsqu’on recherche une méthode, et non pas d’aller chercher dans une classe X ou Y…
- Comme il s’agit d’une méthode
static
, il n’y a aucune possibilité de redéfinir cette méthode. Or cela pourrait être extrêmement avantageux afin d’harmoniser le traitement de la méthode et l’implémentation. Par exemple, dans le cas présent on pourrait opter pour une méthode de tri plus adapté à notre structure de donnée.
Les « public defenders methods » permettent de pallier à tous ces problèmes, en autorisant l’évolution des interfaces. En clair elle permettent de rajouter des méthodes « optionnelles » dans une interface existante en spécifiant l’implémentation qui sera utilisée par défaut lorsque la méthode n’est pas implémentée.
Dans notre cas, pour l’interface java.util.List cela donnerait donc quelque chose comme cela (notez-bien que la syntaxe n’est pas définitive) :
public interface List<E> extends Collection<E> {
// Définitions des méthodes de base :
...
// Ajout des nouvelles méthodes "optionnelles"
extension void sort()
default Collections.sort;
extension void sort(Comparator<? super E> c)
default Collections.<E>sort;
}
Note : le paramétrage fortement typé de Collections.sort() pourrait poser des problèmes, mais c’est une autre histoire…
L’interface possède donc désormais deux nouvelles méthodes sort()
:
- On peut désormais appeler la méthode le plus simplement du monde :
list.sort()
. - La méthode apparaitra dans la javadoc et dans la complétion des EDIs (il faudra peut-être les adapter un peu mais ce ne doit pas être bien méchant), ce qui permet un accès plus facile et plus logique.
- La méthode est bien déclarée dans l’interface, et de ce fait elle peut donc être redéfinie dans une implémentation spécifique en cas de besoin. En fait son implémentation est optionnelle
Comment ça marche ?
Ces méthodes définissent une implémentation par défaut, il n’est donc pas obligatoire de les implémenter (même si cela reste possible). Ce nouveau cas particulier implique deux modifications distinctes du compilateur et le la machine virtuelle, afin de « rajouter » les méthodes absentes si besoin.
Lorsqu’on compile une classe, le compilateur ajoutera une méthode « bridge » pour toutes les « defenders methods » non-implémentées. En clair le compilateur rajoutera lui-même le code de la méthode en se contentant d’appeler la méthode static
, un peu comme si on avait soit-même saisie le code suivant :
public void sort() {
Collections.sort(this);
}
public void sort(Comparator<? super E> c) {
Collections.sort(this, c);
}
Cela permet au code recompilé d’implémenter ces nouvelles méthodes même si elle ne sont pas réellement présente dans le code source, et donc de respecter les règles originales qui veut qu’une classe non-abstraite implémente toutes les méthodes de l’interface. Ainsi les méthodes optionnelles absentes sont ajoutées automatiquement par le compilateur.
Toutefois ce n’est pas suffisant, car cela impliquerait toujours l’obligation de tout recompiler en cas de modification d’une interface. En effet les classes compilées précédemment ne possèdent pas ces méthodes de bridges, ni d’implémentation des méthodes ajoutées par la suite. C’est là que rentre en compte la machine virtuelle, qui a l’avantage de pouvoir manipuler le code juste avant son exécution. En fait la JVM effectuera le même procédé que le compilateur, en injectant les méthodes bridges dans la classe lors de son chargement en mémoire.
Ainsi, si la classe a été compilé avec la dernière version de l’interface, le compilateur aura ajouté automatiquement les méthodes absentes, et elle sera alors à jour et correct. Sinon, la JVM se chargera de cela à l’exécution.
Des effets « secondaires » ?
Reste à voir le problème que cela pourrait engendrer au niveau des ambiguïtés et des conflits de noms. Ce n’est pas réellement un nouveau problème puisque l’on le rencontre déjà lors de l’ajout d’une méthode dans une classe non-final, mais il est bon de le souligner.
Il y a également le retour sur le devant de la scène Java d’un problème qu’on croyait oublié : les conflits d’héritage multiple. Java évitait cela grâce à l’absence d’héritage multiple, mais on peut se retrouver dans un cas similaire si on implémente deux interfaces proposant chacune la même « defender method« …
Mais cela s’avère malgré tout très intéressant et relativement propre !
12 Commentaires + Ajouter un commentaire
Tutoriels
Discussions
- jre 1.5, tomcat 6.0 et multi processeurs
- Difference de performances Unix/Windows d'un programme?
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- Définition exacte de @Override
- Recuperation du nom des parametres
- Possibilité d'accéder au type générique en runtime
- [ fuite ] memoire
- Classes, méthodes private
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
@Baptiste Wicht : j’ai l’impression qu’en fait, on utilise déjà des méthodes « optionnelles » sans y faire attention, dès qu’on utilise l’interface List par exemple : les méthodes « add » et « remove » peuvent lever une UnsupportedOperationException (cf. Javadoc) qui ne sont pas très différente d’une NotImplementedException.
C’est en particulier l’exception qui remonte quand on utilise ces méthodes sur des tableaux « transformés » en listes par Arrays.asList(T…)
@Uther : oui mais les cas ambigus sont bien plus rare !
Il faut :
Que la classe implémente 2 interfaces.
Que chacun de ces interfaces définissent la même méthode d’extension, mais avec une méthode par défaut différente.
Que la classe n’implémente pas cette méthode.
a++
Dans mon idée NotImplementedException serait une exception non checkée.
De toute façon, avec la proposition actuelle, il y aura des exceptions dans les cas ambigus détectés au Runtime.
@Uther : J’aime pas vraiment l’idée d’avoir quelque chose qui est optionnel… T’imagines le code qui va utiliser ça ? A chaque fois, on devra vérifier l’expception NotImplementedException et que faire alors ? Ce sera tellement pas pratique qu’on ne l’utilisera pas. Avec l’implémentation actuelle on n’a pas plus de désavantages qu’avec les méthodes habituelles et on est sûr qu’il y a au moins une implémentation de fournie.
En y réfléchissant d’avantage, je me demande, s’il n’aurait pas mieux valu faire plus simple, c’est a dire mettre un mot clé « optional » ou « extention », sans donner de méthode par défaut. L’utilisation d’une méthode « optional » dans une classe qui ne la redéfinit pas entrainerait un warning a la compilation et une exception du style « NotImplementedException » a l’exectution.
– ca devrait permettre d’éviter les cas ambigus.
– ca éviterait le détournement des interfaces pour un usage de type classe abstraite, qui n’est normalement pas le leur.
Ta remarque sur le mot clé « extension » est déjà signalé dans le document PDF :
A noter également que le PDF se termine par une vision plus éloigné concernant l’évolution de l’API. En gros des mécanismes similaires (modifications de classes à la compilation OU à l’exécution) pourraient être engagé pour supporter d’autres évolutions de l’API.
Ils y parles de déprécation, de migration des signatures de méthodes ou de changement de classe parente. Bref de corriger les petites erreurs de conception qu’on se coltine depuis bien longtemps…
a++
Ce modèle me plait bien à moi aussi. J’apprécie énormément que cela permette une vraie évolution des l’interface par leur concepteurs sans obliger l’utilisateur à déclarer quoi que ce soit de plus.
Je pense par contre qu’il vaut mieux se passer du mot clé « extension ». Le « default » seul me semble suffisant et ca évitera d’éventuels problèmes de compatibilité puise qu’il s’agit déjà d’un mot réservé.
Il me semble justement que c’est très différent…
Sauf erreur les méthodes d’extension ne sont qu’un simple « sucre syntaxique ».
En particulier il me semble qu’il n’y a aucun lien avec l’interface ou la classe « étendu » (quid de la doc et de l’intégration dans les EDI ?), et surtout il n’y a pas moyen de la redéfinir dans une implémentation spécifique.
a++
Mouai, en fait, ce sont les méthodes d’extension de C#… on verra bientôt du « linq » en java?
Ceci dit, je ne dis pas que les bonnes idées ne doivent pas être recopiées…
C’est corrigé merci
Désolé pour le premier commentaire, mais le blog n’aime pas les generics dans les commentaires
Collections.sort(c);
devrait être :
Collections.sort(this, c);
Très bon billet
Juste une petite erreur :
public void sort(Comparator