Why does the declaration look like this:
default > Comparator thenComparing(
Function super
TL;DR:
Comparator.thenComparing(Function< ? super T, ? extends U > keyExtractor)
(the method your question specifically asks about) might be declared that way as an idiomatic/house coding convention thing that the JDK development team is mandated to follow for reasons of consistency throughout the API.
The long-winded version
„…But I don't get this part:
Function super T, ? extends U>
…“
That part is placing a constraint on the specific type that the Function
must return. It sounds like you got that part down already though.
The U
the Function
returns is not just any old U
, however. It must have the specific properties (a.k.a „bounds“) declared in the method's parameter section: >
.
„…Why not just have:
Function super T, U>
…“
To put it as simply as I can (because I only think of it simply; versus formally): The reason is because U
is not the same type as ? extends U
.
Changing Comparable< ? super U >
to List< ? super U >
and Comparator< T >
to Set< T >
might make your quandary easier to reason about…
default < U extends List< ? super U > > Set< T > thenComparing(
Function< ? super T, ? extends U > keyExtractor ) {
T input = …;
/* Intuitively, you'd think this would be compliant; it's not! */
/* List< ? extends U > wtf = keyExtractor.apply( input ); */
/* This doesn't comply to „U extends List< ? super U >“ either */
/* ArrayList< ? super U > key = keyExtractor.apply( input ); */
/* This is compliant because key is a „List extends List< ? super U >“
* like the method declaration requires of U
*/
List< ? super U > key = keyExtractor.apply( input );
/* This is compliant because List< E > is a subtype of Collection< E > */
Collection< ? super U > superKey = key;
…
}
„Can't the
U
just parameterize to whatever thekeyExtractor
returns, and still extendComparable super U>
all the same?…“
I have established experimentally that Function< ? super T, ? extends U > keyExtractor
could indeed be refactored to the the more restrictive Function< ? super T, U > keyExtractor
and still compile and run perfectly fine. For example, comment/uncomment the /*? extends*/
on line 27 of my experimental UnboundedComparator to observe that all of these calls succeed either way…
…
Function< Object, A > aExtractor = ( obj )-> new B( );
Function< Object, B > bExtractor = ( obj )-> new B( ) ;
Function< Object, C > cExtractor = ( obj )-> new C( ) ;
UnboundedComparator.< Object, A >comparing( aExtractor ).thenComparing( bExtractor );
UnboundedComparator.< Object, A >comparing( bExtractor ).thenComparing( aExtractor );
UnboundedComparator.< Object, A >comparing( bExtractor ).thenComparing( bExtractor );
UnboundedComparator.< Object, B >comparing( bExtractor ).thenComparing( bExtractor );
UnboundedComparator.< Object, B >comparing( bExtractor ).thenComparing( aExtractor );
UnboundedComparator.< Object, B >comparing( bExtractor ).thenComparing( cExtractor );
…
Technically, you could do the equivalent debounding in the real code. From the simple experimentation I've done — on thenComparing()
specifically, since that's what your question asks about — I could not find any practical reason to prefer ? extends U
over U
.
But, of course, I have not exhaustively tested every use case for the method with and without the bounded ?
.
I would be surprised if the developers of the JDK haven't exhaustively tested it though.
My experimentation — limited, I admit — convinced me that Comparator.thenComparing(Function< ? super T, ? extends U > keyExtractor)
might be declared that way for no other reason than as an idiomatic/house coding convention thing that the JDK development team follows.
Looking at the code base of the JDK it's not unreasonable to presume that somebody somewhere has decreed: «Wherever there's a Function< T, R >
the T
must have a lower bound (a consumer/you input something) and the R
must have an upper bound (a producer/you get something returned to you)».
For obvious reasons though, U
is not the same as ? extends U
. So the former should not be expected to be substitutable for the latter.
Applying Occam's razor: It's simpler to expect that the exhaustive testing the implementers of the JDK have done has established that the U
-upper bounded wildcard is necessary to cover a wider number of use cases.