Cela provient de l'interface Stream de l'implémentation de JDK 8 par Oracle:
public interface Stream<T> extends BaseStream<T, Stream<T>> {
Stream<T> sorted();
}
et il est très facile de faire exploser cela au moment de l'exécution et aucun avertissement ne sera généré au moment de la compilation. Voici un exemple:
class Foo {
public static void main(String[] args) {
Arrays.asList(new Foo(), new Foo()).stream().sorted().forEach(f -> {});
}
}
qui compilera très bien mais lèvera une exception au moment de l'exécution:
Exception in thread "main" Java.lang.ClassCastException: Foo cannot be cast to Java.lang.Comparable
Quelle pourrait être la raison pour laquelle la méthode sorted
n'a pas été définie et que le compilateur pourrait réellement détecter de tels problèmes? Peut-être que je me trompe, mais n'est-ce pas aussi simple?
interface Stream<T> {
<C extends Comparable<T>> void sorted(C c);
}
?
Il est évident que les personnes implémentant ceci (qui ont des années-lumière devant moi en matière de programmation et d'ingénierie) doivent avoir une très bonne raison pour laquelle je suis incapable de voir, mais quelle est cette raison?
En gros, vous demandez s’il est possible de dire au compilateur, " hé, cette méthode nécessite que le paramètre de type corresponde à des limites plus spécifiques que celles définies au niveau de la classe ". Ce n'est pas possible en Java. Une telle fonctionnalité peut être utile, mais je m'attendrais aussi à être confus et/ou compliqué.
Il n’existe également aucun moyen de rendre Stream.sorted()
type-safe avec la manière dont les génériques sont actuellement implémentés; pas si vous voulez éviter de requérir un Comparator
. Par exemple, vous proposiez quelque chose comme:
public interface Stream<T> {
<C extends Comparable<? super T>> Stream<T> sorted(Class<C> clazz);
} // other Stream methods omitted for brevity
Malheureusement, rien ne garantit que Class<C>
Est assignable à partir de Class<T>
. Considérez la hiérarchie suivante:
public class Foo implements Comparable<Foo> { /* implementation */ }
public class Bar extends Foo {}
public class Qux extends Foo {}
Vous pouvez maintenant avoir un Stream
sur Bar
éléments, mais essayez de le trier comme s'il s'agissait d'un Stream
sur Qux
éléments.
Stream<Bar> stream = barCollection.stream().sorted(Qux.class);
Puisque Bar
et Qux
correspondent Comparable<? super Foo>
, Il n'y a pas d'erreur de compilation et aucune sécurité de type n'est ajoutée. En outre, l'implication d'un argument Class
implique que celui-ci sera utilisé pour le casting. Comme indiqué ci-dessus, au moment de l'exécution, il est toujours possible d'obtenir ClassCastException
s. Si le Class
n'est pas utilisé pour la conversion, l'argument est complètement inutile; Je considérerais même cela comme dangereux.
La prochaine étape logique consiste à essayer de requérir C
prolonger T
ainsi que Comparable<? super T>
. Par exemple:
<C extends T & Comparable<? super T>> Stream<T> sorted(Class<C> clazz);
Cela n’est pas non plus possible dans Java et entraîne une erreur de compilation: "le paramètre type ne peut pas être suivi par d’autres limites". Même si cela était possible, je ne pense pas que cela résoudrait tout. (si quoi que ce soit).
Quelques notes connexes.
En ce qui concerne Stream.sorted(Comparator)
: Ce n'est pas le Stream
qui rend cette méthode sûre, mais le Comparator
. Le Comparator
permet de comparer les éléments. Pour illustrer ceci, la méthode de tri type d'un Stream
par ordre naturel des éléments est:
Stream<String> stream = stringCollection.stream().sorted(Comparator.naturalOrder());
Ceci est sécuritaire pour le type car naturalOrder()
requiert son paramètre de type extend Comparable
. Si le type générique de Stream
ne s'étend pas Comparable
, les limites ne correspondent pas, ce qui entraîne une erreur de compilation. Mais encore une fois, c’est le Comparator
qui nécessite que les éléments soient Comparable
* alors que Stream
ne s'en soucie tout simplement pas.
La question est donc de savoir pourquoi les développeurs ont inclus une méthode sans argument sorted
pour Stream
en premier lieu. Cela semble être pour des raisons historiques et est expliqué dans ne réponse à une autre question par Holger.
* Le Comparator
nécessite que les éléments soient Comparable
dans ce cas. En général, un Comparator
est évidemment capable de gérer tous les types pour lesquels il est défini.
Le documentation pour Stream#sorted
l'explique parfaitement:
Renvoie un flux composé des éléments de ce flux, triés selon leur ordre naturel. Si les éléments de ce flux ne sont pas comparables, une exception Java.lang.ClassCastException peut être levée lors de l'exécution de l'opération de terminal.
Vous utilisez la méthode surchargée qui n'accepte aucun argument (pas celui qui accepte un Comparator
), et Foo
n'implémente pas Comparable
.
Si vous demandez pourquoi la méthode ne génère pas d’erreur de compilation si le contenu de Stream
n’implémente pas Comparable
, c’est que T
n’est pas obligé de extend Comparable
, et T
ne peut être modifié sans un appel à Stream#map
; il semble ne s'agir que d'une méthode de commodité, il n'est donc pas nécessaire de fournir explicitement Comparator
lorsque les éléments implémentent déjà Comparable
.
Pour qu'il soit sécurisé au type, T
devrait être étendu Comparable
, mais ce serait ridicule, car cela empêcherait un flux de contenir des objets qui ne sont pas Comparable
.
Comment mettriez-vous cela en œuvre? sorted
est une opération intermédiaire (peut être appelée n'importe où entre d'autres opérations intermédiaires), ce qui signifie que vous pouvez commencer avec un flux qui n'est pas comparable, mais appelez sorted
sur celle qui estComparable
:
Arrays.asList(new Foo(), new Foo())
.stream()
.map(Foo::getName) // name is a String for example
.sorted()
.forEach(f -> {});
La chose que vous proposez prend un argument en entrée, mais Stream::sorted
ne le fait pas, vous ne pouvez donc pas le faire. La version surchargée accepte un Comparator
, ce qui signifie que vous pouvez trier quelque chose par une propriété, mais que vous retournez quand même Stream<T>
. Je pense que c'est assez facile à comprendre si vous essayez d'écrire votre squelette minimal d'interface/implémentation Stream.