如何解决Java然后比较通配符签名 为什么是? extends U而不是U?有什么实际区别吗?示例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>
为什么声明看起来像这样:
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返回的值,并且仍然以相同的方式扩展Comparable<? super U>
吗?
解决方法
为什么是? extends U
而不是U
?
由于代码约定。请查看@deduper's answer以获得很好的解释。
有什么实际区别吗?
在正常编写代码时,编译器会针对T
和Supplier<T>
之类的东西推断出正确的Function<?,T>
,因此没有实际理由编写Supplier<? extends T>
或{ {1}},当开发API时。
但是,如果我们手动指定类型 ,会怎样?
Function<?,? extends T>
-
如您所见,
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>) {}
无需显式声明就可以正常工作,因为strict()
被推断为T
以匹配局部变量的泛型类型。 -
然后当我们尝试将
Integer
传递为Supplier<Integer>
时会中断,因为Supplier<Number>
和Integer
不兼容。> -
然后与
Number
一起使用,因为fluent()
和? extends Number
兼容。
在实践中,只有当您具有多个泛型类型时,才需要显式地指定其中一个泛型,而错误地获取另一个(Integer
一种),例如:
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
,
TL; DR :
Comparator.thenComparing(Function< ? super T,? extends U > keyExtractor)
(您的问题专门询问的方法) 可能 作为惯用/内部编码约定,JDK开发团队出于整个API的一致性原因而必须遵循。
冗长的版本
„ …但是我没有得到这一部分:
Function<? super T,? extends U>
…”
该部分将约束放在 Function
必须 返回的特定类型上。听起来好像已经把那部分压倒了。
U
返回的 Function
不仅仅是任何旧的 U
。它 必须 具有在方法的参数部分中声明的特定属性( aka“界限” ): <U extends Comparable<? super U>>
。
„ ...为什么不仅仅拥有
Function<? super T,U>
…”
尽可能简单地表达它(因为我只是简单地考虑;相对于形式上):原因是因为U
与{ {1}} 。
将 ? extends U
更改为 Comparable< ? super U >
,将 List< ? super U >
更改为 {{1} } 可能会使您的难题更容易推论……
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; … }
设置为参数,并且仍然将U
扩展为相同的样子吗?…“
I have established experimentally认为 keyExtractor
确实可以重构为 限制性更高的 {{1 }} ,并且仍然可以正常运行。例如,对my experimental UnboundedComparator
的第27行的 Comparable<? super U>
进行注释/取消注释,以观察到所有这些调用都以两种方式都可以成功……
Function< ? super T,? extends U > keyExtractor
从技术上讲,您 可以 在the real code中进行等效的去边界。通过我做过的简单实验-具体是在Function< ? super T,U > keyExtractor
上,因为这就是您的问题-我找不到任何实际的理由偏爱 /*? extends*/
超过 …
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 );
…
。
但是,当然,对于有和没有边界 thenComparing()
的方法,我都没有详尽测试每个用例。
如果the developers of the JDK尚未进行详尽的测试,我会感到惊讶。
My experimentation-我承认有限-说服我 ? extends U
可能 以这种方式声明为JDK开发团队遵循的惯用/内部编码约定。
看看the code base of the JDK并假设某人某人已颁布法令是不合理的:« 无论哪里有U
?
有一个下限(一个消费者/您输入了一些东西),而Comparator.thenComparing(Function< ? super T,? extends U > keyExtractor)
必须有一个上限(生产者/您得到的东西还给您)»。
由于明显的原因, Function< T,R >
与 T
不同。因此,不应期望前者可以替代后者。
使用Occam的剃须刀 :更容易期望JDK实施者进行的详尽测试已经确定 {{1} } -上限通配符对于覆盖更广泛的用例是必需的。。
,您的问题似乎与一般的类型实参有关,因此为了简单起见,在我的回答中,我将把您提供的类型实参与它们所属的类型分开。
首先,我们应该注意,通配符的参数化类型无法访问其具有相应type参数的成员。这就是为什么在您的特定情况下,? extends U
可以代替U
并仍然可以正常工作的原因。
这并非在所有情况下都有效。类型参数U
不具有? extends U
的多功能性和附加类型安全性。通配符是唯一的类型自变量,其中参数化类型的实例化(带有通配符类型自变量)不受类型自变量的限制,而不是类型自变量是具体类型或类型自变量的情况。通配符基本上是占位符,比类型参数和具体类型(用作类型参数时)更通用。 The first sentence in the java tutorial on wild cards读为:
在通用代码中,称为通配符的问号(?)代表未知类型。
为说明这一点,请看一下
class A <T> {}
现在让我们对该类进行两个声明,一个声明为具体类型,另一个声明为通配符,然后我们将其实例化
A <Number> aConcrete = new A <Integer>(); // Compile time error
A <? extends Number> aWild = new A<Integer>() // Works fine
因此,这应该说明通配符类型参数如何不像具体类型那样限制实例化。但是类型参数呢?使用类型参数的问题最好体现在一种方法中。为了说明这一节课:
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
}
请注意引用c
和a
是具体类型。现在在另一个答案中解决了这个问题,但是在另一个答案中没有解决的是类型实参的概念与编译时错误之间的关系(为什么一个类型实参会导致编译时错误,而另一个则不会)关系是使用声明的语法声明所声明的原因。而且这种关系是附加的类型安全性和通用性通配符,它们提供了类型参数而不是某些输入约定。现在,为了说明这一点,我们必须为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();
可能会导致堆污染。由于这个原因,当带有类型参数参数的方法被传递给任何类型的对象而没有在类型参数声明类中进行强制转换时,编译器将发出编译时错误。同样,如果类型参数的成员实例化到任何对象,而没有从类型参数的声明类中进行强制转换,则该参数将发出编译时错误。这里要强调的真正重要的事情是没有强制转换,因为您仍然可以将对象传递给带有类型为parameter的参数的方法,但是必须将其强制转换为该类型参数(或者在这种情况下,必须强制转换为包含type参数的类型)。在我的例子中
void test() {
C <Number> c = new C();
A<Integer> a = new A();
c.parameterMethod(a); // Compile time error
c.wildMethod(a); // Works fine
}
如果将c.parameterMethod(a)
强制转换为a
,则A<U>
将起作用,因此,如果该行看起来像这样c.parameterMethod((A<U>) a);
,则不会发生编译时错误,但是会得到一个如果在调用int
之后尝试将a.something
设置为等于parameterMethod()
的变量,则会发生运行时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
}
因此,因为可以以强制转换的形式引用类型参数,所以将对象从声明类的类型参数内传递给具有类型参数或包含类型参数的方法是非法的。无法以强制转换的形式引用通配符,因此a
中的wildMethod(A<? extends U> a)
无法访问A的T成员;由于这种附加的类型安全性,因为使用通配符避免了发生堆污染的可能性,所以Java编译器的确允许在C<Number> c = new C()
中由引用c调用时将具体类型传递给wildMethod而不进行强制转换。同样,这就是为什么可以在不进行强制转换的情况下将参数化类型的通配符实例化为具体类型的原因。当我说类型参数的通用性时,我在说的是它们在参数化类型的角色中允许哪些实例化。当我说其他类型安全性时,我指的是无法引用以避开heapPollution的强制转换形式引用通配符。
我不知道为什么有人会强制转换类型参数。但是我确实知道开发人员至少会喜欢通配符和类型参数的多功能性。我可能对此写得令人困惑,或者可能是误解了您的问题,在我看来,您的问题似乎是关于类型实参的,而不是这个特定的声明。同样,如果使用声明Function<? super T,? extends U> keyExtractor
中的keyExtractor的方式从未访问过属于第二类型参数的Function
的成员,那么通配符也是理想的,因为通配符无法访问那些无论如何成员;那么为什么开发人员不希望通配符提供此处提到的多功能性?这只是一个好处。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。