Java 然后比较通配符签名

Java thenComparing wildcard signature

为什么声明看起来像这样:

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

大部分我都听懂了。 U 可以是任何东西,只要它可以与自身的超类相比较,因此也可以与自身相比较。

但我不明白这部分:Function<? super T, ? extends U>

为什么不只拥有:Function<? super T, U>

U 不能只参数化任何 keyExtractor returns,并且仍然扩展 Comparable<? super U> 吗?

为什么是 ? extends U 而不是 U

由于代码约定。查看 以获得很好的解释。

有什么实际区别吗?

正常编写代码时,编译器会为 Supplier<T>Function<?, T> 等推断出正确的 T,因此没有实际理由编写 Supplier<? extends T>Function<?, ? extends T> 在开发 API.

但是如果我们手动指定类型会发生什么?

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. 如您所见,strict() 无需显式声明即可正常工作,因为 T 被推断为 Integer 以匹配局部变量的通用类型。

  2. 然后当我们尝试将 Supplier<Integer> 作为 Supplier<Number> 传递时它会中断,因为 IntegerNumber 不是 兼容。

  3. 然后它与 fluent() 一起工作,因为 ? extends NumberInteger 兼容的。

实际上只有当你有多个泛型类型时才会发生这种情况,需要明确指定其中一个并错误地获取另一个(Supplier 一个),例如:

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);

示例Comparator(原始答案)

考虑以下 Comparator.comparing 方法签名:

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

这里还有一些测试 类 层次结构:

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

class B extends A { }

现在让我们试试这个:

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>

TL;DR:

Comparator.thenComparing(Function< ? super T, ? extends U > keyExtractor)你的问题具体询问的方法可能 被声明为 idiomatic/house 编码约定,JDK 开发团队出于整个 API 的一致性原因必须遵循。 =52=]


long-winded版本

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

那部分是在 Function 必须return。听起来你已经把那部分弄下来了。

U Function return 不只是任何旧的 [=然而82=]U。它必须具有在方法的参数部分:<U extends Comparable<? super U>>.

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

尽可能简单地说(因为我只是简单地想到它;而不是正式地):原因是因为U? extends U.

的类型不同

Comparable< ? super U >更改为List< ? super U >Comparator< T >Set< T > 可能会让你的困境更容易推理......

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 观察所有这些调用都成功了......

…
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 );
…

从技术上讲,您可以the real code中执行等效的去绑定。从我做过的简单实验中——具体来说,thenComparing() 上,因为这就是你的问题所问的——我找不到任何更喜欢 [ 的实际理由=21=]超过U.

但是,当然,我并没有详尽地测试该方法的每个用例,无论是否使用有界 ? .

如果 the developers of the JDK 没有对它进行详尽的测试,我会感到惊讶。

My experimentation有限,我承认 — 说服我 Comparator.thenComparing(Function< ? super T, ? extends U > keyExtractor) 可能 以这种方式声明,除了作为 JDK 开发团队遵循的 idiomatic/house 编码约定外,没有其他原因。

看着 the code base of the JDK 假设某个地方的某个人已经颁布法令并非不合理:«只要Function< T, R > T 必须有一个下界a consumer/you输入一些东西R 必须 有一个上限 (a producer/you 得到一些东西 returned to you)».

虽然显而易见,U? extends U 不同。所以不能指望前者可以替代后者。

应用奥卡姆剃刀期望[=188=的实施者的详尽测试更简单] 已经确定 U -上限通配符对于覆盖更多用例是必要的.

您的问题似乎与一般类型参数有关,因此在我的回答中,为简单起见,我会将您提供的类型参数与它们所属的类型分开。

首先我们应该注意,通配符的参数化类型无法访问其属于相应类型参数的成员。这就是为什么在您的特定情况下 ? extends U 可以替换 U 并且仍然可以正常工作。

这并非在所有情况下都有效。类型参数 U 不具有 ? extends U 所具有的多功能性和额外的类型安全性。通配符是一种独特的类型参数,其中参数化类型(使用通配符类型参数)的实例化不受类型参数的限制,就像类型参数是具体类型或类型参数时那样;通配符基本上是比类型参数和具体类型(用作类型参数时)更通用的占位符。 The first sentence in the java tutorial on wild cards 读取:

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

为了说明这一点,看看这个

class A <T> {}

现在让我们对这个 class 做两个声明,一个是具体类型,另一个是通配符,然后我们将实例化它们

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

所以这应该说明通配符类型参数如何不像具体类型那样限制实例化。但是类型参数呢?使用类型参数的问题最好体现在方法上。为了说明这个 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
    }

注意引用 ca 是具体类型。现在这在另一个答案中得到解决,但另一个答案中没有解决的是类型参数的概念与编译时错误的关系(为什么一个类型参数导致编译时错误而另一个不)和这个relation 是使用声明的语法声明相关声明的原因。这种关系是额外的类型安全性和通用性通配符提供了类型参数,而不是一些类型约定。现在为了说明这一点,我们必须给 A 类型参数的成员,所以:

class A<T> { T something; }

在 parameterMethod() 中使用类型参数的危险在于,可以以强制转换的形式引用类型参数,从而可以访问 something 成员。

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

这反过来又增加了堆污染的可能性。使用 parameterMethod 的这种实现,test() 方法中的语句 C<Number> c = new C(); 可能会导致堆污染。出于这个原因,当带有参数类型参数的方法被传递给任何对象而没有从类型参数声明 class 中进行强制转换时,编译器会发出编译时错误;同样,如果在类型参数的声明 class 中将类型参数的成员实例化为任何对象而没有强制转换,类型参数的成员将发出编译时错误。这里要强调的真正重要的事情是 没有强制转换 因为您仍然可以将对象传递给具有类型参数参数的方法,但它必须转换为该类型参数(或者在这种情况下,转换为包含类型参数的类型)。在我的例子中

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

如果将 a 强制转换为 A<U>c.parameterMethod(a) 将起作用,因此如果该行看起来像这样 c.parameterMethod((A<U>) a);,则不会发生编译时错误,但您会如果您在调用 parameterMethod() 之后尝试将 int 变量设置为等于 a.something,则会出现 运行 time castclassexection 错误(同样,编译器需要强制转换,因为 U 可以代表任何东西)。整个场景如下所示:

    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
    }

因此,因为类型参数可以以强制转换的形式引用,所以从类型参数声明 class 中将对象传递给具有类型参数参数或包含的方法是非法的一个类型参数。无法以强制转换的形式引用通配符,因此 wildMethod(A<? extends U> a) 中的 a 无法访问 A 的 T 成员;由于这种额外的类型安全性,由于通配符避免了这种堆污染的可能性,java 编译器确实允许在 C<Number> c = new C() 中的引用 c 调用时将具体类型传递给 wildMethod 而无需强制转换];同样,这就是为什么可以将通配符的参数化类型实例化为具体类型而无需强制转换的原因。当我说类型参数的多功能性时,我是在谈论它们在参数化类型的角色中允许的实例化;当我说额外的类型安全时,我指的是无法以绕过 heapPollution 的强制转换形式引用通配符。

我不知道为什么有人会转换类型参数。但我知道开发人员至少会喜欢通配符与类型参数的多功能性。我可能把这个写得很混乱,或者可能误解了你的问题,在我看来你的问题似乎是关于一般的类型参数而不是这个特定的声明。此外,如果声明 Function<? super T, ? extends U> keyExtractor 中的 keyExtractor 以成员属于的方式使用永远不会访问到 Function 的第二个类型参数,然后再一次,通配符是理想的,因为它们无论如何都不可能访问这些成员;那么为什么开发人员不想要这里提到的通配符提供的多功能性呢?这只是一个好处。