218

Note: this question originates from a dead link which was a previous SO question, but here goes...

See this code (note: I do know that this code won't "work" and that Integer::compare should be used -- I just extracted it from the linked question):

final ArrayList <Integer> list 
    = IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList());

System.out.println(list.stream().max(Integer::max).get());
System.out.println(list.stream().min(Integer::min).get());

According to the javadoc of .min() and .max(), the argument of both should be a Comparator. Yet here the method references are to static methods of the Integer class.

So, why does this compile at all?

fge
  • 119,121
  • 33
  • 254
  • 329
  • 6
    Note that it doesn't work properly, it should be using `Integer::compare` instead of `Integer::max` and `Integer::min`. – Christoffer Hammarström Jun 11 '15 at 09:02
  • @ChristofferHammarström I know that; note how I said before the code extract "I know, it is absurd" – fge Jun 11 '15 at 09:13
  • 3
    I wasn't trying to correct you, i'm telling people in general. You made it sound as if you thought that the part that is absurd is that methods of `Integer` are not methods of `Comparator`. – Christoffer Hammarström Jun 11 '15 at 09:16

5 Answers5

244

Let me explain what is happening here, because it isn't obvious!

First, Stream.max() accepts an instance of Comparator so that items in the stream can be compared against each other to find the minimum or maximum, in some optimal order that you don't need to worry too much about.

So the question is, of course, why is Integer::max accepted? After all it's not a comparator!

The answer is in the way that the new lambda functionality works in Java 8. It relies on a concept which is informally known as "single abstract method" interfaces, or "SAM" interfaces. The idea is that any interface with one abstract method can be automatically implemented by any lambda - or method reference - whose method signature is a match for the one method on the interface. So examining the Comparator interface (simple version):

public Comparator<T> {
    T compare(T o1, T o2);
}

If a method is looking for a Comparator<Integer>, then it's essentially looking for this signature:

int xxx(Integer o1, Integer o2);

I use "xxx" because the method name is not used for matching purposes.

Therefore, both Integer.min(int a, int b) and Integer.max(int a, int b) are close enough that autoboxing will allow this to appear as a Comparator<Integer> in a method context.

Koray Tugay
  • 22,894
  • 45
  • 188
  • 319
David M. Lloyd
  • 2,805
  • 1
  • 12
  • 10
  • 28
    or alternatively: `list.stream().mapToInt(i -> i).max().get()`. – assylias Mar 21 '14 at 14:49
  • 14
    @assylias You want to use `.getAsInt()` instead of `get()` though, as you are dealing with an `OptionalInt`. – skiwi Mar 21 '14 at 14:59
  • ... when what we are only trying is to provide a custom comparator to a `max()` function! – Manu343726 Mar 22 '14 at 09:50
  • It's worth noting that this "SAM Interface" is actually called a "Functional Interface" and that looking at the `Comparator` documentation we can see that it's decorated with the annotation `@FunctionalInterface`. This decorator is the magic that allows `Integer::max` and `Integer::min` to be converted into a `Comparator`. – Chris Kerekes Oct 21 '16 at 20:58
  • 2
    @ChrisKerekes the decorator `@FunctionalInterface` is principally for documentation purposes only, as the compiler can happily do this with any interface with one single abstract method. – errantlinguist Jan 15 '17 at 15:40
118

Comparator is a functional interface, and Integer::max complies with that interface (after autoboxing/unboxing is taken into consideration). It takes two int values and returns an int - just as you'd expect a Comparator<Integer> to (again, squinting to ignore the Integer/int difference).

However, I wouldn't expect it to do the right thing, given that Integer.max doesn't comply with the semantics of Comparator.compare. And indeed it doesn't really work in general. For example, make one small change:

for (int i = 1; i <= 20; i++)
    list.add(-i);

... and now the max value is -20 and the min value is -1.

Instead, both calls should use Integer::compare:

System.out.println(list.stream().max(Integer::compare).get());
System.out.println(list.stream().min(Integer::compare).get());
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    I know about the functional interfaces; and I know why it gives the wrong results. I just wonder how on earth the compiler didn't yell at me – fge Mar 21 '14 at 14:39
  • @fge Because it is technically correct, but I wonder if there is a way that this could be prevented? – skiwi Mar 21 '14 at 14:40
  • 6
    @fge: Well was it the unboxing you were unclear about? (I haven't looked into exactly that part.) A `Comparator` would have `int compare(Integer, Integer)`... it's not mind-boggling that Java allows a method reference of `int max(int, int)` to convert to that... – Jon Skeet Mar 21 '14 at 14:40
  • 7
    @fge: Is the compiler supposed to know the semantics of `Integer::max`? From its perspective you passed in a function that met its specification, that's all it can really go on. – Mark Peters Mar 21 '14 at 14:40
  • 6
    @fge: In particular, if you understand *part* of what's going on, but are intrigued about one specific aspect of it, it's worth making that clear in the question to avoid people wasting their time explaining the bits you already know. – Jon Skeet Mar 21 '14 at 14:44
  • 20
    I think the underlying problem is the type signature of `Comparator.compare`. It should return an `enum` of `{LessThan, GreaterThan, Equal}`, not an `int`. That way, the functional interface wouldn't actually match and you would get a compile error. IOW: the type signature of `Comparator.compare` doesn't adequately capture the semantics of what it means to compare two objects, and thus other interfaces which have absolutely nothing to do with comparing objects accidentally have the same type signature. – Jörg W Mittag Mar 22 '14 at 15:39
  • @JörgWMittag This is definitely worthy of an answer in its own right. You've summed it up perfectly. If you post this as an answer then I'd upvote it. – Michael Jan 28 '19 at 10:31
19

This works because Integer::min resolves to an implementation of the Comparator<Integer> interface.

The method reference of Integer::min resolves to Integer.min(int a, int b), resolved to IntBinaryOperator, and presumably autoboxing occurs somewhere making it a BinaryOperator<Integer>.

And the min() resp max() methods of the Stream<Integer> ask the Comparator<Integer> interface to be implemented.
Now this resolves to the single method Integer compareTo(Integer o1, Integer o2). Which is of type BinaryOperator<Integer>.

And thus the magic has happened as both methods are a BinaryOperator<Integer>.

Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
skiwi
  • 66,971
  • 31
  • 131
  • 216
  • It's not entirely correct to say that `Integer::min` implements `Comparable`. It is not a type that can implement anything. But it is evaluated into an object which implements `Comparable`. – Lii Apr 02 '14 at 10:54
  • 1
    `Comparator` is a single-abstract-method (aka "functional") interface, and `Integer::min` fulfills its contract, so the lambda can be interpreted as this. I don't know how you see BinaryOperator coming into play here (or IntBinaryOperator, either) – there is no subtyping relationship between that and Comparator. – Paŭlo Ebermann Oct 26 '17 at 09:07
2

Apart from the information given by David M. Lloyd one could add that the mechanism that allows this is called target typing.

The idea is that the type the compiler assigns to a lambda expressions or a method references does not depend only on the expression itself, but also on where it is used.

The target of an expression is the variable to which its result is assigned or the parameter to which its result is passed.

Lambda expressions and method references are assigned a type which matches the type of their target, if such a type can be found.

See the Type Inference section in the Java Tutorial for more information.

Lii
  • 11,553
  • 8
  • 64
  • 88
1

I had an error with an array getting the max and the min so my solution was:

int max = Arrays.stream(arrayWithInts).max().getAsInt();
int min = Arrays.stream(arrayWithInts).min().getAsInt();
JPRLCol
  • 749
  • 11
  • 28