En règle générale, le code source Java est compatible avec les versions antérieures. Jusqu'en Java 8, autant que je sache, les deux classes compilées et source étaient compatibles avec les versions ultérieures de JDK/JVM. [Mise à jour: ceci n’est pas correct, voir les commentaires concernant «enum», etc., ci-dessous.] Cependant, avec l’ajout des méthodes par défaut dans Java 8, cela ne semble plus être le cas.
Par exemple, une bibliothèque que j'ai utilisée a une implémentation de Java.util.List
qui inclut une List<V> sort()
. Cette méthode retourne une copie du contenu de la liste triée. Cette bibliothèque, déployée en tant que dépendance de fichier jar, a bien fonctionné dans un projet en cours de construction à l'aide de JDK 1.8.
Cependant, plus tard, j’ai eu l’occasion de recompiler la bibliothèque elle-même en utilisant JDK 1.8 et J’ai constaté que la bibliothèque ne compilait plus: la classe List
-implementation avec sa propre méthode sort()
est maintenant en conflit avec la méthode par défaut Java 8 Java.util.List.sort()
. La méthode par défaut Java 8 sort()
trie la liste sur place (renvoie void
); La méthode sort()
de ma bibliothèque - puisqu'elle renvoie une nouvelle liste triée - a une signature incompatible.
Donc ma question fondamentale est:
Également:
Voici un exemple de code compilé et exécuté sous 1.7 et .__ fonctionnant sous 1.8 - mais ne compilant pas sous 1.8:
import Java.util.*;
public final class Sort8 {
public static void main(String[] args) {
SortableList<String> l = new SortableList<String>(Arrays.asList(args));
System.out.println("unsorted: "+l);
SortableList<String> s = l.sort(Collections.reverseOrder());
System.out.println("sorted : "+s);
}
public static class SortableList<V> extends ArrayList<V> {
public SortableList() { super(); }
public SortableList(Collection<? extends V> col) { super(col); }
public SortableList<V> sort(Comparator<? super V> cmp) {
SortableList<V> l = new SortableList<V>();
l.addAll(this);
Collections.sort(l, cmp);
return l;
}
}
}
Ce qui suit montre ce code en cours de compilation (ou d’échec) et en cours d’exécution.
> c:\tools\jdk1.7.0_10\bin\javac Sort8.Java
> c:\tools\jdk1.7.0_10\bin\Java Sort8 this is a test
unsorted: [this, is, a, test]
sorted : [this, test, is, a]
> c:\tools\jdk1.8.0_05\bin\Java Sort8 this is a test
unsorted: [this, is, a, test]
sorted : [this, test, is, a]
> del Sort8*.class
> c:\tools\jdk1.8.0_05\bin\javac Sort8.Java
Sort8.Java:46: error: sort(Comparator<? super V>) in SortableList cannot implement sort(Comparator<? super E>) in List
public SortableList<V> sort(Comparator<? super V> cmp) {
^
return type SortableList<V> is not compatible with void
where V,E are type-variables:
V extends Object declared in class SortableList
E extends Object declared in interface List
1 error
JDK 1.8 n'introduit-il pas une incompatibilité directe pour le code source Java en raison de méthodes par défaut?
Toute nouvelle méthode dans une super-classe ou une interface peut compromettre la compatibilité. Les méthodes par défaut rendent moins probable qu'un changement d'interface rompe la compatibilité. En ce sens que les méthodes par défaut ouvrent la porte à l'ajout de méthodes aux interfaces, vous pouvez dire que les méthodes par défaut peuvent contribuer à une compatibilité défectueuse.
Est-ce le premier changement incompatible de ce type?
Presque certainement pas, puisque nous avons sous-classé les classes de la bibliothèque standard depuis Java 1.0.
Cela at-il été pris en compte ou discuté lors de la conception et de la mise en œuvre des méthodes par défaut? Est-ce documenté n'importe où?
Oui, c'était envisagé. Voir l'article de Brian Goetz d'août 2010 "Evolution de l'interface via les méthodes du" défenseur public "" :
- Compatibilité source
Il est possible que ce schéma introduise des incompatibilités de source dans la mesure où les interfaces de bibliothèque sont modifiées pour insérer de nouvelles méthodes incompatibles avec les méthodes des classes existantes. (Par exemple, si une classe a une méthode xyz () à valeur flottante et implémente Collection, et que nous ajoutons une méthode xyz () à valeur int à Collection, la classe existante ne sera plus compilée.)
L'inconvénient (certes mineur) a-t-il été réduit par rapport aux avantages?
Avant, changer une interface définitivement romprait la compatibilité. Maintenant, il pourrait. Aller de «définitivement» à «peut-être» peut être vu de manière positive ou négative. D'une part, il est possible d'ajouter des méthodes aux interfaces. D'autre part, cela ouvre la porte au type d'incompatibilité que vous avez constaté, pas seulement avec les classes, mais aussi avec les interfaces.
Les avantages sont plus importants que les inconvénients, cependant, cités en haut du papier de Goetz:
- Déclaration du problème
Une fois publié, il est impossible d'ajouter des méthodes à une interface sans rompre les implémentations existantes. Plus une bibliothèque a été publiée depuis longtemps, plus il est probable que cette restriction causera du tort à ses responsables.
L'ajout de fermetures au langage Java dans JDK 7 impose des contraintes supplémentaires aux interfaces vieillissantes de Collection. L'un des avantages les plus importants des fermetures est qu'elles permettent le développement de bibliothèques plus puissantes. Il serait décevant d'ajouter une fonctionnalité de langage qui permette de meilleures bibliothèques sans pour autant étendre les bibliothèques de base pour en tirer parti.
JDK 1.8 n'introduit-il pas une incompatibilité directe pour le code source Java en raison de méthodes par défaut?
Oui, comme vous l'avez vu.
Est-ce le premier changement incompatible de ce type?
Non, Java 5 enum
keyword cassait également, car auparavant, vous pouviez avoir des variables nommées qui ne seraient plus compilées en Java 5 +.
Cela at-il été envisagé ou discuté lorsque les méthodes par défaut ont été conçues et mises en œuvre? Est-ce documenté n'importe où?
Oui Description de l'incompatibilité de la source d'Orcale Java 8
Les inconvénients (certes mineurs) ont-ils été réduits par rapport aux avantages?
Oui
Nous pouvons établir un parallèle avec la classe abstraite. Une classe abstraite est destinée à être sous-classée afin que les méthodes abstraites puissent être implémentées. La classe abstraite elle-même contient des méthodes concrètes qui invoquent les méthodes abstraites. La classe abstraite est libre d'évoluer en ajoutant des méthodes plus concrètes; et cette pratique peut casser des sous-classes.
Par conséquent, le problème exact que vous avez décrit existait même avant Java8. Le problème est beaucoup plus manifeste sur les API de collection car il existe de nombreuses sous-classes dans la nature.
Si la méthode par défaut visait principalement à ajouter des méthodes utiles aux API de collection existantes sans rompre les sous-classes, ils ont dû faire preuve d'une grande maîtrise de soi, car ils craignaient de rompre les sous-classes. Une méthode par défaut est ajoutée uniquement si c'est absolument nécessaire. La vraie question est de savoir pourquoi List.sort
est considéré comme absolument nécessaire. Je pense que c'est discutable.
Quelle que soit la raison pour laquelle la méthode par défaut a été introduite à la 1ère place, c’est maintenant un excellent outil pour les concepteurs d’API, et nous devons la traiter de la même manière que les méthodes concrètes dans les classes abstraites - elles doivent être conçues avec soin; et les nouveaux doivent être introduits avec beaucoup de prudence.
Ironiquement, des méthodes par défaut dans les interfaces ont été introduites pour permettre aux bibliothèques existantes utilisant ces interfaces not de se décomposer, tout en introduisant une fonctionnalité massive new dans les interfaces. (rétrocompatibilité.)
Des conflits tels que cette méthode sort
peuvent survenir. Quelque chose à payer pour la fonctionnalité supplémentaire. Dans votre cas, il faut également examiner quelque chose (faut-il plutôt utiliser une nouvelle fonctionnalité?).
Les ruptures de compatibilité ascendante de Java sont minimes, plus dans son système de dactylographie, qui a été constamment élargi. D'abord avec les types génériques et maintenant avec les types déduits des interfaces fonctionnelles. De version en version et de compilateur en compilateur, il y avait de légères différences.
En lisant ce numéro, je pensais à sa solution.
Les méthodes par défaut ont résolu les problèmes de compatibilité ascendante, mais des problèmes de compatibilité ascendante subsisteront .
Je pense qu'au lieu d'étendre les classes existantes, dans de tels cas, nous pouvons avoir nos interfaces spécifiques à l'application pour ajouter le comportement souhaité à notre classe. Nous pouvons implémenter cette interface spécifique à l'application et l'utiliser.