12

Why does the declaration look like this:

default <U extends Comparable<? super U>> Comparator<T> thenComparing(
            Function<? super T, ? extends U> keyExtractor)

I understand most of it. It makes sense that U can be anything as long as it's comparable to a superclass of itself, and thus also comparable to itself.

But I don't get this part: Function<? super T, ? extends U>

Why not just have: Function<? super T, U>

Can't the U just parameterize to whatever the keyExtractor returns, and still extend Comparable<? super U> all the same?

kng
  • 607
  • 3
  • 9
  • DelfikPro's answer makes sense. I'll wait a bit longer and if no one else chimes in I'll accept his answer – kng Sep 23 '20 at 17:00
  • „*…I'll wait a bit longer…*“ – @kng — How long is «*a bit*»? I have some thoughts I'd like to share with you. But I can't type it out right at this instant. Before I commit to the effort, it'd be good to know it wouldn't be wasted if I'm X minutes too late. – deduper Sep 23 '20 at 18:35
  • Sure no problem, go for it @deduper – kng Sep 23 '20 at 21:02

3 Answers3

16

Why is it ? extends U and not U?

Because of code conventions. Check out @deduper's answer for a great explanation.

Is there any actual difference?

When writing your code normally, your compiler will infer the correct T for things like Supplier<T> and Function<?, T>, so there is no practical reason to write Supplier<? extends T> or Function<?, ? extends T> when developing an API.

But what happens if we specify the type manually?

void test() {
    Supplier<Integer> supplier = () -> 0;

    this.strict(supplier); // OK (1)
    this.fluent(supplier); // OK

    this.<Number>strict(supplier); // compile error (2)
    this.<Number>fluent(supplier); // OK (3)
}

<T> void strict(Supplier<T>) {}
<T> void fluent(Supplier<? extends T>) {}
  1. As you can see, strict() works okay without explicit declaration because T is being inferred as Integer to match local variable's generic type.

  2. Then it breaks when we try to pass Supplier<Integer> as Supplier<Number> because Integer and Number are not compatible.

  3. And then it works with fluent() because ? extends Number and Integer are compatible.

In practice that can happen only if you have multiple generic types, need to explicitly specify one of them and get the other one incorrectly (Supplier one), for example:

void test() {
    Supplier<Integer> supplier = () -> 0;
    // If one wants to specify T, then they are forced to specify U as well:
    System.out.println(this.<List<?>, Number> supplier);
    // And if U happens to be incorrent, then the code won't compile.
}

<T, U> T method(Supplier<U> supplier);

Example with Comparator (original answer)

Consider the following Comparator.comparing method signature:

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
    Function<? super T, U> keyExtractor
)

Also here is some test classes hierarchy:

class A implements Comparable<A> {
    public int compareTo(A object) { return 0; }
}

class B extends A { }

Now let's try this:

Function<Object, B> keyExtractor = null;
Comparator.<Object, A>comparing(keyExtractor); // compile error
error: incompatible types: Function<Object,B> cannot be converted to Function<? super Object,A>
DelfikPro
  • 729
  • 4
  • 10
  • Would be nice to elaborate on the last sentence. I guess this has to do with the type for `U` getting correctly inferred as `A` if I just pass a matching method reference (without it's explicit declaration), right? – Trinova Sep 24 '20 at 09:15
  • Exactly, the compiler will infer the right generic type for you, no need for explicit declaration. – DelfikPro Sep 24 '20 at 15:06
  • 2
    This is all true and correct, but it doesn't apply for the case in this question. It makes no sense to explicitly provide generic type params for the method and then use another type for the funcion, i.e. I find your `Comparator.comparing(keyExtractor)` example convoluted. You could just use your `Function keyExtractor` and call the method with that one, and it will compile just fine. This is just a matter of coding style, there's nothing more about it – fps Sep 24 '20 at 17:01
  • You're answering this question, DelfikPro: «*If a caller invokes a method with a signature different from what is declared, will compilation fail?*» But that's not really the question @kng is asking, I don't think. The way I interpret what's really being asked is more like: «*If the `keyExtractor` were declared to return a `U` instead of a `? extends U`, would `thenComparing()` still **behave** exactly the same as it did originally?*» However, please correct me if I've misinterpreted you, kng? TIA. – deduper Sep 24 '20 at 19:59
  • @deduper, I believe, you answered the question "Why did jdk developers write this code in such manner?", and I anwered the question "Is there actually any difference, and if so, what is it?" – DelfikPro Sep 24 '20 at 21:14
  • My question was pretty open ended (in my mind), so any difference or non-difference that can be pointed out, would be of interest to me. In any case, it was helpful to test my own thinking/logic against both your answers. And as such I upvoted both. I'll wait for any other answers until the end of this week as I had initially intended. Since sweeper put up a bounty maybe he/she can chime in as well – kng Sep 24 '20 at 21:45
  • @kng I agree with fps and deduper here. The "specifying generic parameters" argument is quite convoluted. I can't remember the last time I had to specify generic parameters. – Sweeper Sep 25 '20 at 09:34
  • It is the only difference I am aware of, and it can theoretically happen in methods with multiple generic parameters - if you want to specify one, then you will be forced to specify the other as well. I agree that this happens as often as **never**, but you asked for a difference - so there you go – DelfikPro Sep 25 '20 at 10:05
  • When I asked my question I had strongly suspected that it would work the same, with or without the "extends", in the majority of cases. (Although it was nice to verify, thanks to deduper). So I expected any answer to be some niche case that I may have missed. DelfikPro's answer is definitely very niche, but he/she does also state that there's no real practical difference, so I'll accept it. AFAIK sweeper can still award the full bounty to whoever he/she wants – kng Sep 25 '20 at 19:59
  • „*…The "specifying generic parameters" argument is quite convoluted…*“ – @Sweeper — As the bounty starter, I presume you're OK with this: [***How is a bounty awarded?***](https://stackoverflow.com/help/bounty): „*If you do not award your bounty within 7 days (plus the grace period), the highest voted answer created after the bounty started with a minimum score of 2 will be awarded half the bounty amount (or the full amount, if the answer is also accepted)*“? – deduper Sep 29 '20 at 20:06
  • @deduper I am fully aware of that :-) I usually wait until the end of the bounty period, so that the question can get as much attention as possible. – Sweeper Sep 30 '20 at 00:03
  • „*…I usually wait until the end of the bounty period…*“ – @Sweeper — Awesome! And for me, it was well worth the wait! :¬) Thanks both you and kng for bringing such a terrific question to our attention. I learned a ton investigating it. I was actually expecting to find the exact opposite of what I did find. It's like I always say: *Always keep an open mind. Ya just might learn something!* – deduper Sep 30 '20 at 00:47
8

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: <U extends Comparable<? super U>>.

…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 the keyExtractor returns, and still extend Comparable<? 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 experimentationlimited, 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.

deduper
  • 1,944
  • 9
  • 22
  • 1
    This is absolutely correct. In this specific case, it would be exactly the same to have either `? extends U` or just `U` – fps Sep 24 '20 at 16:29
  • Comment the `? extends` on line **19 (not 27)** or put explicit generics before `thenComparing(...)` method (you did so only for `comparing(...)`) and you will get a bunch of compile errors. – DelfikPro Sep 24 '20 at 16:35
  • @DelfikPro I went into this experiment with a hypothesis that *`keyExtractor`* would ***behave*** differently based on its return type. Functionality-wise, there's nothing that *`thenCompare()`* can do with a returned *`? extends U`* that it can't do with a *`U`*. From a method signature angle, yes, the *`U`* versus *`? extends U`* distinction indeed makes a difference ***to the caller***. However, ***internal to the method***, as long as the method can do *`U`*-ish things on what's returned from *`keyExtractor`*, it's immaterial whether *`keyExtractor`* returns a *`U`* or a *`? extends U`*. – deduper Sep 24 '20 at 18:35
  • I understand that there is no practical difference, but then it seems either craftily or unneccessary for you to specify generic types explicitly for `comparing()` and not for `thenComparing()` – DelfikPro Sep 24 '20 at 21:00
  • „*it seems either craftily or unneccessary for you to specify generic types explicitly for `comparing()` and not for `thenComparing()`*“ – @DelfikPro — Zero (`0`) thought went into that. Honest. I was genuinely impressed by your results. So I copied/pasted code from your answer, simply to learn something about *`Comparator`* from it. What you now see in my answer is nothing more than the result of me not paying super close attention to what I was changing/not changing, as my own experiment organically developed. An innocent oversight. Forgetfulness? Sure. Haste? Yes. Craftiness? No. Trust me. – deduper Sep 24 '20 at 21:29
  • "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" this is the point of my stack post you tried to answer a while ago titled "What is the benefit of an upper bounded wildcard" in which you claimed the benefit was *covariance*. Notice how covariance applies to both wildcards and type parameters, so this claim is ludicrous. Both `U` and `? extends U` are covariant; this explains the result of your "experiment" (i.e. why either will work in this case) – Matthew S. Sep 26 '20 at 14:51
1

It seems like your question is regarding type arguments in general so for my answer I will be separating the type arguments you provided from the types they belong to, in my answer, for simplicity.

First we should note that a parameterized type of wildcard is unable to access its members that are of the respective type parameter. This is why, in your specific case the ? extends U can be substituted for U and still work fine.

This won't work in every case. The type argument U does not have the versatility and additional type safety that ? extends U has. Wildcards are a unique type argument in which instantiations of the parameterized types (with wildcard type arguments) are not as restricted by the type argument as they would be if the type argument was a concrete type or type parameter; wildcards are basically place holders that are more general than type parameters and concrete types (when used as type arguments). The first sentence in the java tutorial on wild cards reads:

In generic code, the question mark (?), called the wildcard, represents an unknown type.

To illustrate this point take a look at this

class A <T> {}

now let's make two declarations of this class, one with a concrete type and the other with a wild card and then we'll instantiate them

A <Number> aConcrete = new A <Integer>(); // Compile time error
A <? extends Number> aWild = new A<Integer>() // Works fine

So that should illustrate how a wildcard type argument does not restrict the instantiation as much as a concrete type. But what about a type parameter? The problem with using type parameters is best manifested in a method. To illustrate examine this class:

class C <U> {
    void parameterMethod(A<U> a) {}
    void wildMethod(A<? extends U> a) {}
    void test() {
        C <Number> c = new C();
        A<Integer> a = new A();
        c.parameterMethod(a); // Compile time error
        c.wildMethod(a); // Works fine
    }

Notice how the references c and a are concrete types. Now this was addressed in another answer, but what wasn't addressed in the other answer is how the concept of type arguments relate to the compile time error(why one type argument causes a compile time error and the other doesn't) and this relation is the reason why the declaration in question is declared with the syntax it's declared with. And that relation is the additional type safety and versatility wildcards provide over type parameters and NOT some typing convention. Now to illustrate this point we will have to give A a member of type parameter, so:

class A<T> { T something; }

The danger of using a type parameter in the parameterMethod() is that the type parameter can be referred to in the form of a cast, which enables access to the something member.

class C<U> {
    parameterMethod(A<U> a) { a.something = (U) "Hi"; }
}

Which in turn enables the possibility of heap pollution. With this implementation of the parameterMethod the statement C<Number> c = new C(); in the test() method could cause heap pollution. For this reason, the compiler issues a compile time error when methods with arguments of type parameter are passed any object without a cast from within the type parameters declaring class; equally a member of type parameter will issue a compile time error if it is instantiated to any Object without a cast from within the type parameter's declaring class. The really important thing here to stress is without a cast because you can still pass objects to a method with an argument of type parameter but it must be cast to that type parameter (or in this case, cast to the type containing the type parameter). In my example

    void test() {
        C <Number> c = new C();
        A<Integer> a = new A();
        c.parameterMethod(a); // Compile time error
        c.wildMethod(a); // Works fine
    }

the c.parameterMethod(a) would work if a were cast to A<U>, so if the line looked like this c.parameterMethod((A<U>) a); no compile time error would occur, but you would get a run time castclassexection error if you tried to set an int variable equal to a.something after the parameterMethod() is called (and again, the compiler requires the cast because U could represent anything). This whole scenario would look like this:

    void test() {
        C <Number> c = new C();
        A<Integer> a = new A();
        c.parameterMethod((A<U>) a); // No compile time error cuz of cast
        int x = a.something; // doesn't issue compile time error and will cause run-time ClassCastException error
    }

So because a type parameter can be referenced in the form of a cast, it is illegal to pass an object from within the type parameters declaring class to a method with an argument of a type parameter or containing a type parameter. A wildcard cannot be referenced in the form of a cast, so the a in wildMethod(A<? extends U> a) could not access the T member of A; because of this additional type safety, because this possibility of heap pollution is avoided with a wildcard, the java compiler does permit a concrete type being passed to the wildMethod without a cast when invoked by the reference c in C<Number> c = new C(); equally, this is why a parameterized type of wildcard can be instantiated to a concrete type without a cast. When I say versatility of type arguments, I'm talking about what instantiations they permit in their role of a parameterized type; and when I say additional type safety I'm talking about about the inability to reference wildcards in the form of a cast which circumvents heapPollution.

I don't know why someone would cast a type parameter. But I do know a developer would at least enjoy the versatility of wildcards vs a type parameter. I may have written this confusingly, or perhaps misunderstood your question, your question seems to me to be about type arguments in general instead of this specific declaration. Also if keyExtractor from the declaration Function<? super T, ? extends U> keyExtractor is being used in a way that the members belonging to Function of the second type parameter are never accessed, then again, wildcards are ideal because they can't possibly access those members anyway; so why wouldn't a developer want the versatility mentioned here that wildcards provide? It's only a benefit.

Matthew S.
  • 464
  • 2
  • 12
  • I downvoted because this answer is very confusing, and off-topic. At the very least, it misuses common terminology like "concrete type" and "covariant", and consistently confuses "type parameter" and "type variable". It's unclear what a "direct type" is. The answer doesn't address what the question asks, instead going on a tangent about why wildcards are better than type parameters, as if they are comparable things. Other answers on the site explain the use of wildcards much better, eg https://stackoverflow.com/a/5731463/6245827 – oowekyala Sep 28 '20 at 22:15
  • From angelicalanger's glossary on the definition of concrete types:"An instantiation of a generic type where all type arguments are concrete types rather than wildcards . Examples: List Map but not: List extends Number> Map" – Matthew S. Sep 29 '20 at 03:13
  • Section 4.4 of the java SE "A type variable is introduced by the declaration of a type parameter of a generic class, interface, method, or constructor" – Matthew S. Sep 29 '20 at 03:33
  • I understand that concrete type means something else *formally* but informally it's quite common to use it as I used it here. You should check out this stack post here: https://stackoverflow.com/questions/7075363/definition-of-type-variable-and-parameter to better understand the difference between type parameter and type variable (I used them correctly). Also, the question is asking the difference between a type parameter and wild card, how could I not compare them (like the other two answers did)? – Matthew S. Sep 29 '20 at 04:02