我根据name属性(字符串)将ArrayList<Person>
按字母顺序排序。代码按预期工作,但我不明白的是它如何知道a
指的是什么。
如果我没有理解错的话,sort方法需要一个Comparator作为参数,Comparator.comparing()
返回一个Comparator,这个Comparator基本上使sort方法能够对Person对象进行排序。comparing()
方法接受一个函数作为参数,我给它一个匿名函数/lambda,它接受a
作为参数,并执行a.getName()
,返回名称String。
但我真的不明白的是,它怎么知道a
是这里的Person
?甚至IDE都知道,因为它在我编写时建议使用Person中的方法。comparing()
甚至怎么知道a
是Person。我看过Comparator、comparison()和ArrayList.sort()的文档和源代码,但无法弄清楚。
arrayListOfPersons.sort(Comparator.comparing(a -> a.getName()));
1条答案
按热度按时间txu3uszq1#
由外而内分辨率和由内而外分辨率的组合。
基本上,编译器不知道。
a -> a.getName()
是编译器 * 知道 * 是lambda表达式的东西,因为->
不是在其他地方出现的东西。(有a--> b
,但标记器知道它是标识符a
,一元运算符--
,二元运算符>
,标识符b
)。但它不知道
a
是人。所以它只是停留在一个占位符:某种lambda。暂时的。然后,我们从外向内:
Comparator.comparing()的签名如下:
这可能不像人们想象的那么有用,但它确实有帮助。
U
,以及? super T
部分,在这里主要是转移注意力:? super T
而不是T,仅仅是因为,例如,当你特别需要将字符串Map到某个内在可排序的东西时,一个可以将任何对象Map到某个内在可排序的东西的函数可以很容易地“工作”。你“需要”的可能性非常低,但它是一个核心库,也需要满足奇异的用例。为了理解它是如何工作的,假设它改为T
。U
完全是应用于函数中的一个东西,它不是它返回的内容的一部分(因为它返回一个Comparator<T>
,这里没有U
)它 * 只是 * 使用类型系统来确保您将Person
Map到的对象是知道如何对自身排序的对象,也就是说,它是Comparable
,你可能会忘记这里的U
(只要知道,它是为了确保如果你,比如说,将一个人Map到一个输入流,这是一个不知道如何将自己与其他输入流比较的东西,是一个编译时错误)。<T> Comparator<> comparing(Function<T, ?> f)
.现在我们知道这个lambda是
Function<T, ?>
类型的,这意味着a -> a.getName()
是Function
类型中的一个抽象方法的实现,这个抽象方法是B apply(A a)
,这里B
是?
,A
是T
。我们不知道T应该是什么,而
?
是一个死胡同-我们只是将它绑定到可用的最佳选项(Object
),因此,返回a.getName()
肯定是好的(尽管我们还不知道a
是什么;它是T
,但是T
是什么?不知道,还不知道)。我们继续前进:
arrayListOfPersons.sort(X)
,这里只有一个方法它需要一个Comparator<? super E>
,其中E
是列表本身的typevar,我们将其应用到目前为止的工作中:我们希望我们的comparing
函数返回一个Comparator<? super E>
,其中E
引用我们在(arrayListOfPersons
)上调用它的任何类型变量。所以我们...现在知道了!耶!我们需要一个
Comparator<? super Person>
。所以,我们可以绑定一些类型,现在知道了这个函数(a -> a.getName())
必须是Function<? super Person, ?>
。我们没有进一步的信息,所以我们将第一个?
绑定到Person
(为什么不呢,这是我们能做的最好的了,这比绑定到Object
更有用),然后我们就完成了:a -> a.getName()
是方法Object apply(Person p)
的lambda实现,因此我们知道a
(参数)的类型为Person
。a.getName()
很好,您的IDE可以显示Person
的自动完成条目。如果我们暂时抛开泛型,事情就会变得简单得多。
在这里,要弄清楚上面的方法为什么有效就简单多了:只有一个
test
方法,它的参数类型是Example
,Example是一个函数接口(@FunctionalInterface
注解没有使它成为函数接口;任何有1个抽象方法的接口都算数,只是@FI
是编译器检查的文档:如果我们向接口添加更多的抽象方法,编译器会告诉我们,我们违背了原作者的意图)。函数接口中的一个抽象方法是
String foo(Integer i)
,因此x
是Integer
,如果不将其转换为字符串,编译器将生成错误。您的示例是相同的,只是我们需要做大量由内而外和由外而内的工作来绑定类型变量。