web-dev-qa-db-fra.com

Pourquoi Stream.sorted n’est-il pas typable dans Java 8?

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?

38
Koray Tugay

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 ClassCastExceptions. 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.

29
Slaw

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.

18
Jacob G.

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.

15
Eugene