Java 8 stream's .min() and .max(): why does this compile?
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.
Why does Java8 Stream.min() and max() takes comparator as input?
The only way min()
could work without a Comparator
was if the element type T
of the Stream<T>
was bound to implement Comparable<T>
.
However, the Stream
interface should support both element types that implement Comparable
and element types that don't. Therefore, you must pass a Comparator<T>
to min()
.
Calculating Min and Max sums from a List of Integer using Java 8 streams
The overflow occurs because you are performing reduction on a stream of Integer
, i.e. accumulator of the reduce()
operation deals with Interger
objects, and then you're assigning the overflown result of the calculation to a variable of type long
.
LongSummaryStatistics & LongStream
Since only one value should be discarded in both cases (for min and max sum), we can approach this problem by calculating the total of all elements and subtracting the value of the lowest element to obtain the maximum sum and subtracting the value of the highest element to get the minimum sum.
There's no need to apply sorting and hard-code the value of 4
in the stream. If you don't want to develop bad habits, try to design your solutions to be clean and generalized.
We can get all three values (total, the lowest element, the highest element) by performing a single iteration over the given list. It can be done "manually" by tracking these values while iterating with a for
loop, or we can make use of one of the summary statistics classes from the JDK to get this job done for us.
From the summary statistics object we can extract information about the total sum, minimum and maximum values, using its methods getSum()
, getMin() and getMax()
. To avoid int
overflow we have to use LongSummaryStatistics
. And we can obtain this object from a LongStream
by applying summaryStatistics()
as a terminal operation.
public static void miniMaxSum(List<Integer> arr) {
LongSummaryStatistics statistics = arr.stream()
.mapToLong(i -> i)
.summaryStatistics();
long max = statistics.getSum() - statistics.getMin();
long min = statistics.getSum() - statistics.getMax();
System.out.println(min + " " + max);
}
main()
public static void main(String[] args) {
miniMaxSum(List.of(256741038, 623958417, 467905213, 714532089, 938071625));
}
Output:
2063136757 2744467344
Concise way to get both min and max value of Java 8 stream
If this is a frequently needed feature, we better make a Collector
to do the job. We'll need a Stats
class to hold count, min, max
, and factory methods to creat stats collector.
Stats<String> stats = stringStream.collect(Stats.collector())
fooStream.collect(Stats.collector(fooComparator))
(Maybe a better convenience method would be Stats.collect(stream)
)
I made an example Stats
class -
https://gist.github.com/zhong-j-yu/ac5028573c986f7820b25ea2e74ed672
public class Stats<T>
{
int count;
final Comparator<? super T> comparator;
T min;
T max;
public Stats(Comparator<? super T> comparator)
{
this.comparator = comparator;
}
public int count(){ return count; }
public T min(){ return min; }
public T max(){ return max; }
public void accept(T val)
{
if(count==0)
min = max = val;
else if(comparator.compare(val, min)<0)
min = val;
else if(comparator.compare(val, max)>0)
max = val;
count++;
}
public Stats<T> combine(Stats<T> that)
{
if(this.count==0) return that;
if(that.count==0) return this;
this.count += that.count;
if(comparator.compare(that.min, this.min)<0)
this.min = that.min;
if(comparator.compare(that.max, this.max)>0)
this.max = that.max;
return this;
}
public static <T> Collector<T, Stats<T>, Stats<T>> collector(Comparator<? super T> comparator)
{
return Collector.of(
()->new Stats<>(comparator),
Stats::accept,
Stats::combine,
Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH
);
}
public static <T extends Comparable<? super T>> Collector<T, Stats<T>, Stats<T>> collector()
{
return collector(Comparator.naturalOrder());
}
}
Java 8 stream's max(Math::max)
This is somehow very subtle, let's take two at a time:
-2, -5 => Max between these two is "-2"
It is a negative result, since max
method from a stream
accepts a Comparator
, which says:
returns a negative integer if the first argument is less than the second.
Thus according to your Comparator
, you have just said that -5
> -2
or in simpler words:
Stream.of(-2, -5)
.max(Math::max)
.ifPresent(System.out::println); // will show -5
You can build your logic for the other numbers from here and understand why -16
is the result that you get.
To make this correct, you need:
...max(Comparator.naturalOrder())
....
Find the min and max number from a Collection using Java streams
You have to flatten the elements of all the List
s into a single Stream<Integer>
in order for your code to work :
System.out.println(largeList.stream().flatMap(List::stream).max(Integer::compare).get());
System.out.println(largeList.stream().flatMap(List::stream).min(Integer::compare).get());
This is not very efficient, though, since you process the List
s twice in order to find both min
and max
, and you can get the same data (and more) in a single processing by using IntStream::summaryStatistics()
:
IntSummaryStatistics stats = largeList.stream().
.flatMap(List::stream)
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println(stats.getMin());
System.out.println(stats.getMax());
Java 8 streams, why does this compile part 2... Or what is a method reference, really?
The syntax of method references is defined in JLS #15.13. In particular it can be of the form:
Primary :: [TypeArguments] Identifier
Where Primary
can be, among other things, a:
ClassInstanceCreationExpression
so yes, your syntax is correct. A few other interesting examples:
this::someInstanceMethod // (...) -> this.someInstanceMethod(...)
"123"::equals // (s) -> "123".equals(s)
(b ? "123" : "456")::equals // where b is a boolean
array[1]::length // (String[] array) -> array[1].length()
String[]::new // i -> new String[i]
a.b()::c // (...) -> a.b().c(...)
By the way, since you mention static methods, it is interesting to note that you can't create a static method reference from an instance:
class Static { static void m() {} }
Static s = new Static();
s.m(); //compiles
someStream.forEach(s::m); //does not compile
someStream.forEach(Static::m); //that's ok
Related Topics
Bidirectional Multi-Valued Map in Java
Run Main Class of Maven Project
Why Can't I Declare Static Methods in an Interface
Is There a Java Utility to Do a Deep Comparison of Two Objects
How to Use Printwriter and File Classes in Java
Creating a Triangle with for Loops
How to Change the Shape of a Jtabbedpane Tab
Difference Between Arrays.Aslist(Array) and New Arraylist<Integer>(Arrays.Aslist(Array))
How to Intentionally Cause a Custom Java Compiler Warning Message
Hibernate Criteria: Joining Table Without a Mapped Association
Using Jasperreports with a Relative Path
A Regex to Match a Substring That Isn't Followed by a Certain Other Substring
Why Is an Instance Variable of the Superclass Not Overridden by a Subclass
How to Convert a Byte Array into a Double and Back