Traduction d' un article rédigé par Peter Verhas en avril 2014. Du traducteur : le terme « méthode par défaut » vient d'apparaître en Java et je ne sais pas s'il existe une traduction établie en russe pour cela. J'utiliserai le terme « méthode par défaut », même si je ne pense pas que ce soit idéal. Je vous invite à discuter d’une traduction plus réussie.
Quelle est la méthode par défaut
Désormais, avec la sortie de Java 8, vous pouvez ajouter de nouvelles méthodes aux interfaces afin que l'interface reste compatible avec les classes qui l'implémentent. Ceci est très important si vous développez une bibliothèque utilisée par de nombreux programmeurs de Kiev à New York. Avant Java 8, si vous définissiez une interface dans une bibliothèque, vous ne pouviez pas y ajouter de méthodes sans risquer qu'une application exécutant votre interface se brise lors de sa mise à jour. Alors, en Java 8, vous ne pouvez plus avoir peur de ça ? Non tu ne peux pas. L'ajout d'une méthode par défaut à une interface peut rendre certaines classes inutilisables. Examinons d'abord les avantages des méthodes par défaut. En Java 8, la méthode peut être implémentée directement dans l'interface. (Les méthodes statiques dans une interface peuvent désormais également être implémentées, mais c'est une autre histoire.) Une méthode implémentée dans une interface est appelée méthode par défaut et est désignée par le mot-clé default . Si une classe implémente une interface, elle peut, mais ce n'est pas obligatoire, implémenter les méthodes implémentées dans l'interface. La classe hérite de l'implémentation par défaut. C'est pourquoi il n'est pas nécessaire de modifier les classes lors du changement de l'interface qu'elles implémentent.Héritage multiple ?
Les choses deviennent plus compliquées si une classe implémente plus d’une (disons, deux) interfaces et qu’elles implémentent la même méthode par défaut. De quelle méthode la classe héritera-t-elle ? La réponse est aucune. Dans ce cas, la classe doit implémenter elle-même la méthode (soit directement, soit en héritant d'une autre classe). La situation est similaire si une seule interface a une méthode par défaut et que dans l'autre, la même méthode est abstraite. Java 8 essaie d'être discipliné et d'éviter les situations ambiguës. Si les méthodes sont déclarées dans plusieurs interfaces, alors aucune implémentation par défaut n'est héritée par la classe - vous obtiendrez une erreur de compilation. Cependant, vous n'obtiendrez peut-être pas d'erreur de compilation si votre classe est déjà compilée. Java 8 n'est pas assez robuste à cet égard. Il y a des raisons à cela, que je ne veux pas aborder (par exemple : la version Java est déjà sortie et le temps des discussions est révolu depuis longtemps et en général, ce n'est pas le lieu pour elles).- Disons que vous disposez de deux interfaces et qu’une classe les implémente toutes les deux.
- L'une des interfaces implémente la méthode par défaut m().
- Vous compilez toutes les interfaces et la classe.
- Vous modifiez une interface qui n’a pas de méthode m() en la déclarant comme méthode abstraite.
- Vous compilez uniquement l'interface modifiée.
- Commencez le cours.
- changez l'interface avec la méthode abstraite m() et ajoutez une implémentation par défaut.
- Compilez l'interface modifiée.
- Exécuter la classe : erreur.
Exemple de code
Pour démontrer ce qui précède, j'ai créé un répertoire de test pour la classe C.java et 3 sous-répertoires pour les interfaces dans les fichiers I1.java et I2.java. Le répertoire racine du test contient le code source de la classe C.java. Le répertoire de base contient une version des interfaces adaptée à l'exécution et à la compilation : l'interface I1 a une méthode par défaut m(); L'interface I2 ne dispose pas encore de méthodes. La classe a une méthodemain
afin que nous puissions l'exécuter pour la tester. Il vérifie s'il existe des arguments de ligne de commande, nous pouvons donc facilement l'exécuter avec ou sans appeler le m()
.
~/github/test$ cat C.java
public class C implements I1, I2 {
public static void main(String[] args) {
C c = new C();
if( args.length == 0 ){
c.m();
}
}
}
~/github/test$ cat base/I1.java
public interface I1 {
default void m(){
System.out.println("hello interface 1");
}
}
~/github/test$ cat base/I2.java
public interface I2 {
}
Vous pouvez compiler et exécuter la classe à partir de la ligne de commande.
~/github/test$ javac -cp .:base C.java
~/github/test$ java -cp .:base C
hello interface 1
Le répertoire compatible contient une version de l'interface I2 qui déclare la méthode m() comme abstraite, ainsi que, pour des raisons techniques, une copie non modifiée de I1.java.
~/github/test$ cat compatible/I2.java
public interface I2 {
void m();
}
Un tel ensemble ne peut pas être utilisé pour compiler une classe C :
~/github/test$ javac -cp .:compatible C.java
C.java:1: error: C is not abstract and does not override abstract method m() in I2
public class C implements I1, I2 {
^
1 error
Le message d'erreur est très précis. Cependant, nous avons C.class d'une compilation précédente et, si nous compilons les interfaces dans le répertoire compatible, nous aurons deux interfaces qui pourront toujours être utilisées pour exécuter la classe :
~/github/test$ javac compatible/I*.java
~/github/test$ java -cp .:compatible C
hello interface 1
Le troisième répertoire - wrong
- contient la version I2, qui déclare également la méthode m()
:
~/github/test$ cat wrong/I2.java
public interface I2 {
default void m(){
System.out.println("hello interface 2");
}
}
Vous n'avez même pas à vous soucier de la compilation. Même si la méthode est déclarée deux fois, la classe peut toujours être utilisée et exécutée jusqu'à ce que la méthode m() soit appelée. C'est pour cela que nous avons besoin de l'argument de ligne de commande :
~/github/test$ javac wrong/*.java
~/github/test$ java -cp .:wrong C
Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: I1.m I2.m
at C.m(C.java)
at C.main(C.java:5)
~/github/test$ java -cp .:wrong C x
~/github/test$
GO TO FULL VERSION