8

I'm studying for 1z0-809 : Java SE 8 Programmer II using Enthuware's mocktests.

Encountering this question.

List<Integer> ls = Arrays.asList(3,4,6,9,2,5,7);

System.out.println(ls.stream().reduce(Integer.MIN_VALUE, (a, b)->a>b?a:b)); //1
System.out.println(ls.stream().max(Integer::max).get()); //2
System.out.println(ls.stream().max(Integer::compare).get()); //3
System.out.println(ls.stream().max((a, b)->a>b?a:b)); //4   

Which of the above statements will print 9?

Answer is

1 and 3

But there is something else. I don't get why

System.out.println(ls.stream().max(Integer::max).get()); // PRINTS 3

I tried to debug it using peek but it doesn't help me understanding.

I tried to sort ls using Integer::max and Integer::compare

ls.sort(Integer::max);     // [3, 4, 6, 9, 2, 5, 7]
ls.sort(Integer::compare); // [2, 3, 4, 5, 6, 7, 9]

Of course, I get the fact that Integer::max is not a Comparator, hence it has the same signature of one. For me, max should be 7 in the first case since it is the last element like when I sorted with Integer::compare

Could someone break it down to something simple?

Yassin Hajaj
  • 21,337
  • 9
  • 51
  • 89
  • I did not get how `stream.max` worked while given a `Comparator`. That's a bit different – Yassin Hajaj Mar 20 '16 at 20:21
  • How is it different at all? As long as the comparator obeys the contract... – fge Mar 20 '16 at 20:22
  • @fge This one does not obey the contract. That's why it is confusing. Check Tunaki's answer. – Yassin Hajaj Mar 20 '16 at 20:23
  • But you said it yourself: `Integer::max` is not a comparator. So what is the problem? Note that the runtime mechanism only checks whether the _signature_ matches; it won't check the "correctness of behavior" (it cannot, really). – fge Mar 20 '16 at 20:28
  • 2
    @fge First of all, there is no problem. I was curious about why the result was 3. I didn't say it was a correct comparaison. I didn't even speak about the correctness of the behavior at all since the correct answer wasn't 2, it was 1 and 3. I just wanted to know **WHY** the output was 3 and **HOW** did it become 3. :) That's it. – Yassin Hajaj Mar 20 '16 at 20:31

5 Answers5

11

Integer.max(a, b) will return the greater value of the given a and b. If you use that result somehow as a comparator, a positive value returned will be regarded as meaning that a > b so a will be kept.

The first two elements are 3 and 4. Both are positive. Integer.max(3, 4) = 4 > 0. So you're effectively saying that 3 > 4 with such a comparator, so 3 is kept. Then, the same goes for the rest: Integer.max(3, 6) = 6 > 0, so 3 is considered the max, etc.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • Thank you very much that's what I needed ! So it keeps the max of the previous ones in memory and goes on the next ones until the end. – Yassin Hajaj Mar 20 '16 at 20:17
  • 2
    Actually the result of this case is unspecified. You're feeding the `.max` with broken comparator, so you can get any result. It's not specified how exactly `.max` will compare the input. – Tagir Valeev Mar 21 '16 at 05:44
  • @TagirValeev Isn't it possible to predict the output? – Yassin Hajaj Mar 21 '16 at 13:36
  • 1
    @YassinHajaj, it's possible looking into concrete implementation. But you cannot guarantee that next java update (even minor) will not change the implementation. – Tagir Valeev Mar 21 '16 at 14:13
11

You need to replace

.max(Integer::max)

with

.max(Integer::compare)

The problem here is that the abstract method compare is declared to return an int value and that is also satisfied by the signature of Integer.max along with method Integer.compare in the Integer class, hence the representation Integer::max is inferred as a valid Comparator. Though the compare method is expected to be implemented such as it:

Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.

In your current scenario, the code always ends up returning a positive value (given the input) and therefore the first element in comparison is considered to be greater always and thus returned accordingly.

Naman
  • 27,789
  • 26
  • 218
  • 353
  • Instead of `.max(Integer::compare)`, I’d use `.max(Comparator.naturalOrder())`, to avoid the redundant creation of a new `Comparator`. Or use `int i = map.keySet().stream() .mapToInt(Integer::parseInt) .max() .getAsInt();` in the first place… – Holger Nov 18 '19 at 09:45
6

The contract of a comparator method (on arguments first, second) is:

  • return 0 if first equals second
  • return negative value if first < second
  • return positive value if first > second

The max method with only positive values will always return a positive value. When interpreting the return value according to the comparator contract, a positive value means first > second. As a result, the ordering of items will not change, they appear to be "ordered".

janos
  • 120,954
  • 29
  • 226
  • 236
6

This is unbelievably confusing.

We are trying use Integer::max as a comparator. Since all the numbers in the question are positive, the answer is always interpreted as meaning that the first argument of compare(a, b) is "greater" than the second.

One quirk of Collections.sort is that when you have a list [a, b], the method is programmed in such a way that it is compare(b, a) that is called, rather than compare(a, b) (which would seem more sensible). Hence if compare returns a positive number, it looks like b > a, so the list does not need sorting.

That is why list.sort does nothing.

However it just happens that Stream.max is programmed the other way around, i.e. the first comparison is compare(3, 4), so it looks like 3 is the maximum.

Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • 1
    This has nothing to do with the problem at all. Comparison is a _qualitative_ operation, not a _quantitative_ operation. If I compare 234 to 2, the result of the comparison may be 232, 1, 99392 or 98; it _does not matter_. And neither does the order in which arguments are called. – fge Mar 20 '16 at 20:18
  • 3
    @fge It does matter, because `max` does not meet the contract for `compare`. Imagine you try to sort [a, b]. If you do compare(a, b) and get a positive answer, it means that a > b so you need to swap. If you do compare(b, a) and get a positive answer it means that b > a so you do not need to swap. I can assure you that I am correct. – Paul Boddington Mar 20 '16 at 20:21
  • @PaulBoddington Thanks for the answer ! It is indeed really confusing. Was just curious about the result :). – Yassin Hajaj Mar 20 '16 at 20:26
0

Although this doesn't answer the question, a possible solution to your problem would be:

try{
    int i = map.values().stream().mapToInt(e -> e).max().getAsInt();
}catch(NoSuchElementException e)
    e.printStackTrace();
}

The mapToInt(e -> e) method uses a lambda expression to transform every element in the stream to a integer value and returns a IntStream where max() method can then be called. Note that max() returns an OptionalInteger and so a getAsInt() or a orElse(0) method call is nedeed.

You can take a look at the docs for a more in-depth answer: IntStream, OptionalInt

David Urbina
  • 11
  • 1
  • 3
  • Fine solution.Another option is `String maxId = Collections.max(map.keySet(), Comparator.comparingInt(Integer::parseInt));`. – Ole V.V. Nov 16 '19 at 19:04