Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Stream.sorted not type-safe in Java 8?

This is from the Stream interface from Oracle's implementation of JDK 8:

public interface Stream<T> extends BaseStream<T, Stream<T>> {     Stream<T> sorted(); }  

and it is very easy to blow this up at run time and no warning will be generated at compile time. Here is an example:

class Foo {     public static void main(String[] args) {         Arrays.asList(new Foo(), new Foo()).stream().sorted().forEach(f -> {});     } } 

which will compile just fine but will throw an exception at run time:

Exception in thread "main" java.lang.ClassCastException: Foo cannot be cast to java.lang.Comparable 

What could be the reason that the sorted method was not defined where the compiler could actually catch such problems? Maybe I am wrong but isn't it this simple:

interface Stream<T> {     <C extends Comparable<T>> void sorted(C c); } 

?

Obviously the guys implementing this (who are light years ahead of me as far as programming and engineering is considered) must have a very good reason that I am unable to see, but what is that reason?

like image 548
Koray Tugay Avatar asked Nov 09 '18 03:11

Koray Tugay


People also ask

Is Java stream sorted stable?

Stream sorted() in JavaFor ordered streams, the sort method is stable but for unordered streams, no stability is guaranteed.

What is the purpose of sorted method of stream in Java 8?

Found within the Stream interface, the sorted() method has two overloaded variations that we'll be looking into. This methods returns a stream consisting of the elements of the stream, sorted according to natural order - the ordering provided by the JVM. If the elements of the stream are not Comparable , a java.

Why streams in Java 8 are lazy?

Streams are lazy because intermediate operations are not evaluated unless terminal operation is invoked. Each intermediate operation creates a new stream, stores the provided operation/function and return the new stream. The pipeline accumulates these newly created streams.


2 Answers

Essentially, you're asking if there's a way to tell the compiler, "hey, this one method requires the type parameter match more specific bounds than defined at the class level". This is not possible in Java. Such a feature may be useful but I'd also expect confusing and/or complicated.

There's also no way to make Stream.sorted() type-safe with how generics is currently implemented; not if you want to avoid requiring a Comparator. For instance, you were proposing something like:

public interface Stream<T> {      <C extends Comparable<? super T>> Stream<T> sorted(Class<C> clazz);  } // other Stream methods omitted for brevity 

Unfortunately, there's no guarantee that Class<C> is assignable from Class<T>. Consider the following hierarchy:

public class Foo implements Comparable<Foo> { /* implementation */ }  public class Bar extends Foo {}  public class Qux extends Foo {} 

You can now have a Stream of Bar elements but try to sort it as if it was a Stream of Qux elements.

Stream<Bar> stream = barCollection.stream().sorted(Qux.class); 

Since both Bar and Qux match Comparable<? super Foo> there is no compile-time error and thus no type-safety is added. Also, the implication of requiring a Class argument is that it'll be used for casting. At runtime this can, as shown above, still result in ClassCastExceptions. If the Class isn't used for casting then the argument is completely useless; I'd even consider it harmful.

The next logical step is to try and require C extend T as well as Comparable<? super T>. For example:

<C extends T & Comparable<? super T>> Stream<T> sorted(Class<C> clazz); 

This is also not possible in Java and results in a compilation error: "type parameter cannot be followed by other bounds". Even if this were possible, I don't think it'd solve everything (if anything at all).


Some related notes.

Regarding Stream.sorted(Comparator): It isn't the Stream that makes this method type-safe, it's the Comparator. The Comparator ensures the elements can be compared. To illustrate, the type-safe way to sort a Stream by the elements' natural order is:

Stream<String> stream = stringCollection.stream().sorted(Comparator.naturalOrder()); 

This is type-safe because naturalOrder() requires its type parameter extend Comparable. If the generic type of the Stream did not extend Comparable then the bounds wouldn't match, resulting in a compilation error. But again, it's the Comparator that requires the elements be Comparable* while the Stream simply doesn't care.

So the question becomes, why did the developers include a no-argument sorted method for Stream in the first place? It appears to be for historical reasons and is explained in an answer to another question by Holger.


* The Comparator requires the elements be Comparable in this case. In general, a Comparator is obviously capable of handling any type it's defined to.

like image 160
Slaw Avatar answered Sep 28 '22 00:09

Slaw


The documentation for Stream#sorted explains it perfectly:

Returns a stream consisting of the elements of this stream, sorted according to natural order. If the elements of this stream are not Comparable, a java.lang.ClassCastException may be thrown when the terminal operation is executed.

You're using the overloaded method that accepts no arguments (not the one that accepts a Comparator), and Foo does not implement Comparable.

If you're asking why the method doesn't throw a compiler error if the contents of the Stream do not implement Comparable, it would be because T is not forced to extend Comparable, and T cannot be changed without a call to Stream#map; it seems to only be a convenience method so no explicit Comparator needs to be provided when the elements already implement Comparable.

For it to be type-safe, T would have to extend Comparable, but that would be ridiculous, as it would prevent a stream from containing any objects that aren't Comparable.

like image 27
Jacob G. Avatar answered Sep 27 '22 23:09

Jacob G.