Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
547 views
in Technique[技术] by (71.8m points)

generics - Java thenComparing wildcard signature

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

56.9k users

...