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?
The documentation for
Stream#sorted
explains it perfectly:You're using the overloaded method that accepts no arguments (not the one that accepts a
Comparator
), andFoo
does not implementComparable
.If you're asking why the method doesn't throw a compiler error if the contents of the
Stream
do not implementComparable
, it would be becauseT
is not forced to extendComparable
, andT
cannot be changed without a call toStream#map
; it seems to only be a convenience method so no explicitComparator
needs to be provided when the elements already implementComparable
.For it to be type-safe,
T
would have to extendComparable
, but that would be ridiculous, as it would prevent a stream from containing any objects that aren'tComparable
.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 aComparator
. For instance, you were proposing something like:Unfortunately, there's no guarantee that
Class<C>
is assignable fromClass<T>
. Consider the following hierarchy:You can now have a
Stream
ofBar
elements but try to sort it as if it was aStream
ofQux
elements.Since both
Bar
andQux
matchComparable<? super Foo>
there is no compile-time error and thus no type-safety is added. Also, the implication of requiring aClass
argument is that it'll be used for casting. At runtime this can, as shown above, still result inClassCastException
s. If theClass
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
extendT
as well asComparable<? super T>
. For example: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 theStream
that makes this method type-safe, it's theComparator
. TheComparator
ensures the elements can be compared. To illustrate, the type-safe way to sort aStream
by the elements' natural order is:This is type-safe because
naturalOrder()
requires its type parameter extendComparable
. If the generic type of theStream
did not extendComparable
then the bounds wouldn't match, resulting in a compilation error. But again, it's theComparator
that requires the elements beComparable
* while theStream
simply doesn't care.So the question becomes, why did the developers include a no-argument
sorted
method forStream
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 beComparable
in this case. In general, aComparator
is obviously capable of handling any type it's defined to.How would you implement that?
sorted
is a intermediate operation (can be called anywhere between other intermediate operations), meaning you can start with a stream that is not Comparable, but callsorted
on one that isComparable
:The thing that you are proposing takes an argument as input, but
Stream::sorted
does not, so you can't do that. The overload version accepts aComparator
- meaning you can sort something by a property, but still returnStream<T>
. I think that this is quite easy to understand if you would try to write your minimal skeleton of a Stream interface/implementation.