A combination of outside-in resolution and inside-out resolution.
In basis, the compiler has no idea. a -> a.getName()
is something the compile knows is a lambda expression, because ->
isn't a thing that shows up anywhere else. (There's a--> b
but the tokenizer knows that's Identifier a
, unary operator --
, binary operator >
, identifier b
).
But it doesn't know that a
is person.
So it just sticks in a placeholder: Some sort of lambda. For now. Then, we go outside in:
Comparator.comparing()`'s signature here is:
<T, U extends Comparable<? super U>>
Comparator<T>
comparing(Function<? super T, ? extends U> f)
Which isn't as useful as one might think, but it helps. The U
, as well as the ? super T
part, are mostly red herrings here:
It's ? super T
instead of T solely because e.g. a function that can map any object to something inherently orderable can trivially 'work' when you specifically need to map strings to something inherently orderable. The odds you 'need' that are very low, but it's a core library and needs to cater to exotic use cases too. For understanding how this works, just assume it said T
instead.
The U
is entirely a thing that applies within the function, it isn't part of what it returns (as it returns a Comparator<T>
, no U
here). It's just using the type system to ensure that the object you map your Person
to is something that knows how to sort itself, i.e. is Comparable
with itself. You might as well forget the U
here as far as understanding it (just know, it's there to ensure that if you, say, map a person to an inputstream, which isn't a thing that knows how to compare itself to other inputstreams, is a compile time error).
That means this is simply: <T> Comparator<> comparing(Function<T, ?> f)
.
NOW we know that this lambda is for the type Function<T, ?>
. This means a -> a.getName()
is the implementation for the one abstract method in the Function
type, which is B apply(A a)
where in this case B
is ?
and A
is T
.
We have no idea what T is supposed to be, and that ?
is a dead end - we just bind it to the best option available (Object
) and thus, returning a.getName()
is definitely going to be fine (though we don't know what a
is yet; it's T
, but what's T
? Dunno, not yet).
We keep going: arrayListOfPersons.sort(X)
, there's only one method that applies here; it takes a Comparator<? super E>
where E
is the typevar on the list itself. We apply this to our work so far: we desire that our comparing
function returns a Comparator<? super E>
where E
refers to the typevar on whatever we called this on (arrayListOfPersons
). I assume arrayListOfPersons
was declared like so:
List<Person> arrayListOfPersons;
and therefore we... now know E! Yay! We thus want a Comparator<? super Person>
. And therefore, we can bind some types and now know that function (the a -> a.getName())
needs to be a Function<? super Person, ?>
. We have no further info, so we bind the first ?
to Person
(why not, it's the best we can do here, and that is more useful than binding it to Object
), and we're done: a -> a.getName()
is a lambda implementation of the method Object apply(Person p)
, and therefore we know a
(which is the parameter) is of type Person
. a.getName()
is fine, and your IDE can show the autocomplete entries for Person
.
It becomes a lot simpler if we ditch the generics for a moment. Imagine:
@FunctionInterface
interface Example {
String foo(Integer i);
}
void test(Example e) {}
....
test(x -> x.intValue() + "km");
Here it's simpler to figure out why the above works: There is only one test
method, its argument type is Example
, Example is a functional interface (the @FunctionalInterface
annotation doesn't make it one; any interface that has 1 abstract method counts, it's just that @FI
is compiler-checked documentation: If we add more abstract methods to that interface, the compiler will tell us we're breaking the intent of the original author).
The one abstract method in that functional interface is String foo(Integer i)
, therefore the x
is Integer
, and the compiler will generate an error if you don't make a string out of it.
Your example is identical, except we needed to do quite a bit of inside-out and outside-in work to bind up the type vars.