java 在arrayListOfPersons.sort(比较器.comparing(a ->a.getName()))中,它如何知道'a'引用了ArrayList中的Person对象?

q8l4jmvw  于 2023-02-20  发布在  Java
关注(0)|答案(1)|浏览(125)

我根据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()));
txu3uszq

txu3uszq1#

由外而内分辨率和由内而外分辨率的组合。
基本上,编译器不知道。a -> a.getName()是编译器 * 知道 * 是lambda表达式的东西,因为->不是在其他地方出现的东西。(有a--> b,但标记器知道它是标识符a,一元运算符--,二元运算符>,标识符b)。
但它不知道a是人。
所以它只是停留在一个占位符:某种lambda。暂时的。然后,我们从外向内:
Comparator.comparing()的签名如下:

<T, U extends Comparable<? super U>>
   Comparator<T>
   comparing(Function<? super T, ? extends U> f)

这可能不像人们想象的那么有用,但它确实有帮助。U,以及? super T部分,在这里主要是转移注意力:

  • 它是? super T而不是T,仅仅是因为,例如,当你特别需要将字符串Map到某个内在可排序的东西时,一个可以将任何对象Map到某个内在可排序的东西的函数可以很容易地“工作”。你“需要”的可能性非常低,但它是一个核心库,也需要满足奇异的用例。为了理解它是如何工作的,假设它改为T
  • U完全是应用于函数中的一个东西,它不是它返回的内容的一部分(因为它返回一个Comparator<T>,这里没有U)它 * 只是 * 使用类型系统来确保您将PersonMap到的对象是知道如何对自身排序的对象,也就是说,它是Comparable,你可能会忘记这里的U(只要知道,它是为了确保如果你,比如说,将一个人Map到一个输入流,这是一个不知道如何将自己与其他输入流比较的东西,是一个编译时错误)。
  • 这意味着这很简单:<T> Comparator<> comparing(Function<T, ?> f) .

现在我们知道这个lambda是Function<T, ?>类型的,这意味着a -> a.getName()Function类型中的一个抽象方法的实现,这个抽象方法是B apply(A a),这里B?AT
我们不知道T应该是什么,而?是一个死胡同-我们只是将它绑定到可用的最佳选项(Object),因此,返回a.getName()肯定是好的(尽管我们还不知道a是什么;它是T,但是T是什么?不知道,还不知道)。
我们继续前进:arrayListOfPersons.sort(X),这里只有一个方法它需要一个Comparator<? super E>,其中E是列表本身的typevar,我们将其应用到目前为止的工作中:我们希望我们的comparing函数返回一个Comparator<? super E>,其中E引用我们在(arrayListOfPersons)上调用它的任何类型变量。

List<Person> arrayListOfPersons;

所以我们...现在知道了!耶!我们需要一个Comparator<? super Person>。所以,我们可以绑定一些类型,现在知道了这个函数(a -> a.getName())必须是Function<? super Person, ?>。我们没有进一步的信息,所以我们将第一个?绑定到Person(为什么不呢,这是我们能做的最好的了,这比绑定到Object更有用),然后我们就完成了:a -> a.getName()是方法Object apply(Person p)的lambda实现,因此我们知道a(参数)的类型为Persona.getName()很好,您的IDE可以显示Person的自动完成条目。
如果我们暂时抛开泛型,事情就会变得简单得多。

@FunctionInterface
interface Example {
  String foo(Integer i);
}

void test(Example e) {}

....

test(x -> x.intValue() + "km");

在这里,要弄清楚上面的方法为什么有效就简单多了:只有一个test方法,它的参数类型是Example,Example是一个函数接口(@FunctionalInterface注解没有使它成为函数接口;任何有1个抽象方法的接口都算数,只是@FI是编译器检查的文档:如果我们向接口添加更多的抽象方法,编译器会告诉我们,我们违背了原作者的意图)。
函数接口中的一个抽象方法是String foo(Integer i),因此xInteger,如果不将其转换为字符串,编译器将生成错误。
您的示例是相同的,只是我们需要做大量由内而外和由外而内的工作来绑定类型变量。

相关问题