Quelle est la méthode par défaut ?
Après la sortie de Java 8, de nouvelles méthodes pourront être ajoutées à l'interface, mais l'interface restera toujours compatible avec sa classe d'implémentation. Ceci est important car la bibliothèque que vous développez peut être largement utilisée par plusieurs développeurs. Avant Java 8, après la publication d'une interface dans une bibliothèque de classes, si une nouvelle méthode était ajoutée à l'interface, les applications qui implémentaient cette interface risquaient de planter en utilisant la nouvelle version de l'interface.
Avec Java 8, n’y a-t-il pas un tel danger ? La réponse est non.
L'ajout de méthodes par défaut à une interface peut rendre certaines classes d'implémentation indisponibles.
Tout d'abord, regardons les détails de la méthode par défaut.
Dans Java 8, les méthodes dans les interfaces peuvent être implémentées (les méthodes statiques dans Java 8 peuvent également être implémentées dans les interfaces, mais c'est un autre sujet). La méthode implémentée dans l'interface est appelée méthode par défaut, qui est identifiée par le mot-clé default comme modificateur. Lorsqu'une classe implémente une interface, elle peut implémenter des méthodes déjà implémentées dans l'interface, mais ce n'est pas obligatoire. Cette classe héritera de la méthode par défaut. C'est pourquoi lorsque l'interface change, la classe d'implémentation n'a pas besoin d'être modifiée.
Qu’en est-il des héritages multiples ?
Lorsqu'une classe implémente plus d'une (par exemple deux) interfaces et que ces interfaces ont la même méthode par défaut, les choses deviennent très compliquées. De quelle méthode par défaut la classe hérite-t-elle ? Ni l'un ni l'autre! Dans ce cas, la classe elle-même (soit directement, soit une classe située plus haut dans l'arbre d'héritage) doit implémenter la méthode par défaut.
La même chose est vraie lorsqu'une interface implémente la méthode par défaut et qu'une autre interface déclare la méthode par défaut comme abstraite. Java 8 essaie d'éviter toute ambiguïté et de maintenir la rigueur. Si une méthode est déclarée dans plusieurs interfaces, aucune des implémentations par défaut ne sera héritée et vous obtiendrez une erreur de compilation.
Cependant, si vous avez compilé votre classe, il n’y aura aucune erreur de compilation. À ce stade, Java 8 est incohérent. Cela a ses propres raisons, et il y a plusieurs raisons. Je ne veux pas l'expliquer en détail ou en discuter en profondeur ici (car : la version est sortie, le temps de discussion est trop long, et cette plateforme n'a jamais eu une telle discussion).
1. Supposons que vous disposiez de deux interfaces et d’une classe d’implémentation.
2. L'une des interfaces implémente une méthode par défaut m().
3. Compilez ensemble l'interface et la classe d'implémentation.
4. Modifiez l'interface qui ne contient pas la méthode m() et déclarez la méthode m() comme abstraite.
5. Recompilez l'interface modifiée séparément.
6. Exécutez la classe d'implémentation.
1. Modifiez l'interface contenant la méthode abstraite m() et créez une implémentation par défaut.
2. Compilez l'interface modifiée
3. Exécuter la classe : échec.
Lorsque deux interfaces fournissent une implémentation par défaut pour la même méthode, cette méthode ne peut être appelée que si la classe d'implémentation implémente également la méthode par défaut (soit directement, soit par une classe de niveau supérieur dans l'arbre d'héritage).
Exemple de code :
Afin de démontrer l'exemple ci-dessus, j'ai créé un répertoire de test pour C.java, et il contient 3 sous-répertoires pour stocker I1.java et I2.java. Le répertoire test contient le code source C.java de classe C. Le répertoire de base contient la version de l'interface qui peut être compilée et exécutée. I1 contient la méthode m() avec une implémentation par défaut et I2 ne contient aucune méthode.
La classe d'implémentation contient la méthode main afin que nous puissions l'exécuter dans nos tests. Il vérifiera s'il existe des paramètres de ligne de commande, afin que nous puissions facilement effectuer des tests appelant m() et n'appelant pas m().
Copiez le code comme suit :
~/github/test$ cat C.java
la classe publique C implémente I1, I2 {
public static void main (String[] arguments) {
Cc = nouveau C();
si(args.length == 0){
cm();
}
}
}
~/github/test$ catbase/I1.java
interface publique I1 {
par défaut vide m(){
System.out.println("bonjour interface 1");
}
}
~/github/test$ catbase/I2.java
interface publique I2 {
}
Utilisez la ligne de commande suivante pour compiler et exécuter :
Copiez le code comme suit :~/github/test$ javac -cp .:base C.java
~/github/test$ java -cp .:base C
bonjour interface 1
Le répertoire compatible contient l'interface I2 avec la méthode abstraite m() et l'interface I1 non modifiée.
Copiez le code comme suit :~/github/test$ cat compatible/I2.java
interface publique I2 {
vide m();
}
Cela ne peut pas être utilisé pour compiler la classe C :
Copiez le code comme suit :~/github/test$ javac -cp .:compatible C.java
C.java:1 : erreur : C n'est pas abstrait et ne remplace pas la méthode abstraite m() dans I2
la classe publique C implémente I1, I2 {
^
1 erreur
Le message d'erreur est très précis. Parce que nous avons la C.class obtenue dans la compilation précédente, si nous compilons les interfaces dans le répertoire compatible, nous obtiendrons toujours deux interfaces pouvant exécuter la classe d'implémentation :
Copiez le code comme suit :
~/github/test$ compatible javac/I*.java
~/github/test$ java -cp .:compatible C
bonjour interface 1
Le troisième répertoire appelé incorrect contient l'interface I2 qui définit également la méthode m() :
Copiez le code comme suit :
~/github/test$ chat faux/I2.java
interface publique I2 {
par défaut vide m(){
System.out.println("bonjour interface 2");
}
}
Nous devrions prendre la peine de le compiler. Bien que la méthode m() soit définie deux fois, la classe d'implémentation peut toujours s'exécuter tant qu'elle n'appelle pas la méthode définie plusieurs fois. Cependant, tant que nous appelons la méthode m(), elle échouera immédiatement. Voici les paramètres de ligne de commande que nous utilisons :
Copiez le code comme suit :
~/github/test$ javac incorrect/*.java
~/github/test$ java -cp .:mauvais C
Exception dans le thread « principal » java.lang.IncompatibleClassChangeError : conflit
méthodes par défaut : I1.m I2.m
à Cm(C.java)
à C.main(C.java:5)
~/github/test$ java -cp .:mauvais C x
~/github/test$
en conclusion
Lorsque vous portez une bibliothèque de classes qui ajoute une implémentation par défaut à une interface vers l'environnement Java 8, il n'y aura généralement aucun problème. C'est du moins ce que pensaient les développeurs de bibliothèques de classes Java8 lorsqu'ils ont ajouté des méthodes par défaut aux classes de collection. Les applications qui utilisent votre bibliothèque s'appuient toujours sur des bibliothèques Java 7 qui n'ont pas de méthodes par défaut. Lors de l'utilisation et de la modification de plusieurs bibliothèques de classes différentes, il existe un faible risque que des conflits se produisent. Comment cela peut-il être évité ?
Concevez votre bibliothèque de classes comme avant. Ne le prenez pas à la légère lorsque vous vous fiez à la méthode par défaut. Ne pas utiliser en dernier recours. Choisissez judicieusement les noms de méthodes pour éviter les conflits avec d’autres interfaces. Nous apprendrons comment utiliser cette fonctionnalité pour le développement en programmation Java.