-1

I am sorting an ArrayList<Person> into alphabetical order based on the name property (which is a String). The code works as intended, the one part I don't get is how it knows what a refers to.

If I understand it correctly, the sort method takes a Comparator as a parameter. Comparator.comparing() returns a Comparator. This Comparator basically enables the sort method to sort the Person objects into order.

The comparing() method takes a function as a parameter. I am giving it an anonymous function/lambda which has a as a parameter and does a.getName() which returns the name String.

But what I really don't understand is how does it know that a is a Person here? Even the IDE knew as it was suggesting methods from Person as I wrote it. How does comparing() even know that a is a Person. I've had a look in the documentation and source code of Comparator, comparing() and ArrayList.sort() but was unable to figure it out.

arrayListOfPersons.sort(Comparator.comparing(a -> a.getName()));
Tom
  • 29
  • 3
  • Because `sort` tells it so. – Karl Knechtel Feb 19 '23 at 16:42
  • Does this answer your question? [How to explain callbacks in plain english? How are they different from calling one function from another function?](https://stackoverflow.com/questions/9596276/how-to-explain-callbacks-in-plain-english-how-are-they-different-from-calling-o) – Karl Knechtel Feb 19 '23 at 16:43
  • The feature you're looking for is called "type inference". There are loads of Q&As here covering that topic. In particular, although this Q doesn't seem exactly to be a dupe of *[Very confused by Java 8 Comparator type inference](https://stackoverflow.com/questions/24436871/very-confused-by-java-8-comparator-type-inference)*, the two are closely related. – John Bollinger Feb 19 '23 at 16:52

1 Answers1

-1

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.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72