英文:
Generics in Java Comparator class. What is the exact type of T and U?
问题
考虑以下两个方法。它们唯一的区别在于它们对于Function<>的泛型类型声明。
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparingT(
Function<T, ? extends U> keyExtractor) <-- 在这里检查!使用了 T 而不是 ? super T
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
假设我有一个 List<GamingComputer> = { ASUS, MSI }
,其中 GamingComputer
扩展了 Computer
。现在,我想对它们进行排序。
List.sort( comparing( Computer::getProperty ) )
T 的类型是什么?
我的直觉:T=GamingComputer
。comparing()
接受了类型为 Function<Computer super GamingComputer, Property>
的 keyExtractor
。最终,comparing()
返回 Comparator<GamingComputer>
。
这段代码可以证明我的直觉,可以完美地编译通过:
Function<Computer, Property> f1 = Computer::getProperty;
Comparator<GamingComputer> c1 = comparing(f1);
现在,根据 PECS 原则,由于 c1
、c2
被添加到集合/构造函数/方法中,只要集合处理它们的父类,它就可以处理任何子类。这就是 <? super T>
背后的原因。
正如下面的代码所示:
Function<Computer, Property> f2 = Computer::getProperty;
Comparator<GamingComputer> c2 = comparingT(f2); // 无法编译通过。所需类型:Comparator<GamingComputer>,提供的类型:Comparator<Computer>
Comparator<Computer> c2 = comparingT(f2); // 可以成功编译通过
因为 f2
对所有的 Computer
都适用,它也应该适用于任何 GamingComputer
。然而,由于我们没有将类型声明为 <? super T>
,所以我们无法构建一个 GamingComputers
的比较器。
有道理。那么...
Comparator<GamingComputer> c22 = comparingT(Computer::getProperty); // 可以成功编译通过... 什么鬼,原谅我的用词
我猜测:使用类型 T=GamingComputer
的 comparingT()
在 keyExtractor
上强制进行了一个向下转型,它是 Computer::getProperty
。它强制所有的 Computer
使用 GamingComputer::getProperty
,这可能不是问题,因为 Comparator<GamingComputer>
确实可以比较 GamingComputers
。
但是,为什么这个代码无法编译通过?
Function<Computer, Property> f22 = GamingComputer::getProperty;
这个错误非常奇怪:
无法从静态上下文中引用非静态方法,这可能是 IntelliJ 的一个 bug。
链接:https://stackoverflow.com/questions/42200958/non-static-method-cannot-be-referenced-from-a-static-context-in-java-8-streams
然而,当编译时:
java: incompatible types: invalid method reference
method getPart in class function.GamingComputer cannot be applied to given types
required: no arguments
found: function.Computer
reason: actual and formal argument lists differ in length
英文:
Consider the two following methods. Their only difference is in their generic type declaration of Function<>
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparingT(
Function<T, ? extends U> keyExtractor) <-- Check here! T instead of ? super T
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
Let's say I have a List<GamingComputer> = { ASUS, MSI }
, where GamingComputer
extends Computer
. Now, I want to sort them.
List.sort( comparing( Computer::getProperty ) )
What is the type of T?
My intuition: T=GamingComputer
. comparing()
takes in keyExtractor
, whose type is Function<Computer super GamingComputer, Property>
. Finally, comparing()
returns Comparator<GamingComputer>
.
This code, proving my intuition, compiles perfectly:
Function<Computer, Property> f1 = Computer::getProperty;
Comparator<GamingComputer> c1 = comparing(f1);
Now, by PECS, since c1
, c2
are being added to a collection/ constructor/ method, as long as the collection handles their parent class, it could handle any child class. That's the reason behind <? super T>
.
As demonstrated in this code:
Function<Computer, Property> f2 = Computer::getProperty;
Comparator<GamingComputer> c2 = comparingT(f2); // FAILS to compile. Required Type: Comparator<GamingComputer>, Provided Comparator<Computer>
Comparator<Computer> c2 = comparingT(f2); // compiles successfuly
Since f2
works with all Computer
, it should be able to work with any GamingComputer
as well. However, because we did not declare type as <? super T>
, we are unable to construct a Comparator
of GamingComputers
.
Makes sense. Then...
Comparator<GamingComputer> c22 = comparingT(Computer::getProperty); // compiles successfuly... WT, excuse mi French, heck???
My guess: comparingT()
with type T=GamingComputer
forces a downcast on keyExtractor
, which is Computer::getProperty
. It forces all Computers
to use GamingComputer::getProperty
, which is probably not an issue, since Comparator<GamingComputer>
does compare GamingComputers
.
But, why does this NOT compile?
Function<Computer, Property> f22 = GamingComputer::getProperty;
The error is very peculiar:
Non-static method cannot be referenced from a static context, which is probably a bug from Intellij
Still, when compiling:
java: incompatible types: invalid method reference
method getPart in class function.GamingComputer cannot be applied to given types
required: no arguments
found: function.Computer
reason: actual and formal argument lists differ in length
答案1
得分: 1
我的直觉:
T=GamingComputer
你的直觉是正确的。
为什么
Comparator<GamingComputer> c22 = comparingT(Computer::getProperty);
能够编译?这是因为与不变的功能接口实例不同,方法引用是协变和逆变的。使用来自这里的示例,您可以做如下操作:
// 在SomeClass中
public static Integer function(Object o) {
return 2;
}// ...
Function<String, Object> function = SomeClass::function;或者使用您的类,您可以执行以下操作:
Function<GamingComputer, Property> f = Computer::getProperty;
方法引用的参数上“好像”有
? super
,返回类型上有? extends
!详细信息可在Java语言规范的第15.13.2节中找到。因此对于c22
,T
仍然是GamingComputer
。方法引用Computer::getProperty
可以转换为Function<GamingComputer, Property>
,因为它是一个方法引用。即使
f2
“存储”了Computer::getProperty
,但以下代码不会编译:Comparator<GamingComputer> c2 = comparingT(f2);
因为
f2
本身不是方法引用,它是一个变量。为什么
Function<Computer, Property> f22 = GamingComputer::getProperty;
无法编译?
f22
将能够接受任何类型的Computer
,因为它接受Computer
。如果您给f22
另一种类型的计算机(而不是GamingComputer
),GamingComputer.getProperty
肯定无法处理那种情况,不是吗?
英文:
> My intuition: T=GamingComputer
Your intuition is correct.
<hr>
> Why does Comparator<GamingComputer> c22 = comparingT(Computer::getProperty);
compile?
This is because unlike instances of functional interfaces which are invariant, method references are covariant and contravariant. Using an example from here, you can do something like:
// in SomeClass
public static Integer function(Object o) {
return 2;
}
// ...
Function<String, Object> function = SomeCLass::function;
Or using your classes, you can do:
Function<GamingComputer, Property> f = Computer::getProperty;
It's "as if" method references have ? super
on their parameters and ? extends
on the return types! The details of what works and what doesn't are specified in section 15.13.2 of the Java Language Specification.
So for c22
, T
is still GamingComputer
. The method reference Computer::getProperty
can be converted to Function<GamingComputer, Property>
it's a method reference.
This does not compile, even though f2
"stores" Computer::getProperty
:
Comparator<GamingComputer> c2 = comparingT(f2);
because f2
is not a method reference itself. It is a variable.
> Why does Function<Computer, Property> f22 = GamingComputer::getProperty;
not compile?
f22
would be able to accept any kind of Computer
, since it accepts Computer
. If you give f22
another kind of computer (not GamingComputer
), GamingComputer.getProperty
certainly would not be able to handle that, would it?
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论