ChatGPT解决这个技术问题 Extra ChatGPT

Java 8 stream's .min() and .max(): why does this compile?

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?

Note that it doesn't work properly, it should be using Integer::compare instead of Integer::max and Integer::min.
@ChristofferHammarström I know that; note how I said before the code extract "I know, it is absurd"
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.

K
Koray Tugay

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.


or alternatively: list.stream().mapToInt(i -> i).max().get().
@assylias You want to use .getAsInt() instead of get() though, as you are dealing with an OptionalInt.
... when what we are only trying is to provide a custom comparator to a max() function!
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.
@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.
J
Jon Skeet

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());

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: Well was it the unboxing you were unclear about? (I haven't looked into exactly that part.) A Comparator<Integer> 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...
@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.
@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.
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.
P
Paŭlo Ebermann

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>.


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 Thanks, I fixed it just now.
Comparator<Integer> 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.
L
Lii

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.


J
JPRLCol

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();