Introduction
In this guide, we'll take a look at how to get the maximum or minimum element in a Java Collection, both for primitive types and custom comparable objects, via their fields.
Getting the Maximum or Minimum Element with Collections.max()
The Collections
framework provides us with a wide variety of helper, static methods for working with Collections in Java. It's no wonder that this same framework allows us to search and return the maximum or minimum element of a collection as well.
Collections.max() and Collections.min() with Primitive Types
Working with primitive types is fairly easy in most regards, and as long as they're Comparable
- we can easily search through them.
To find the maximum or minimum element of a Collection consisting of primitive types, we simply call the Collections.max()
or Collections.min()
methods, passing in the collections we're searching in:
List<Integer> list = List.of(1, 5, 4, 3, 7, 6, 9, 4);
Integer min = Collections.min(list);
Integer max = Collections.max(list);
System.out.println(String.format("Minimum element is %s", min));
System.out.println(String.format("Maximum element is %s", max));
Running this code returns our maximum and minimum elements:
Minimum element is 1
Maximum element is 9
Collections.max() and Collections.min() with Custom Objects
Though, we rarely only work with just primitive types. Typically, we'll be working with objects. Naturally, since these structures are much more complex - you get to decide what constitutes a greater element between the two.
Usually, this is achieved by implementing the Comparable
interface, which allows you to compare any two instances of a class to determine which is greater. Let's define a class and make it Comparable
:
public class Book implements Comparable<Book> {
private String author;
private String name;
private int pageNumber;
// Constructor, getters and setters
@Override
public int compareTo(Book book) {
return (this.pageNumber > book.pageNumber) ? 1 : -1;
}
@Override
public String toString() {
return "Book{" +
"author='" + author + '\'' +
", name='" + name + '\'' +
", pageNumber=" + pageNumber +
'}';
}
}
You'll have to @Override
the compareTo()
method and define by which criteria the entities are compared with. It'll typically boil down to simple primitive types in the end, such as comparing the pageNumber
attribute. We've also added a toString()
method for convenient formatting.
Now, searching for the maximum or minimum element, in a collection of custom objects is as easy as calling Collections.max()
or Collections.min()
on the Collection:
List<Book> bookList = new ArrayList<>();
bookList.add(new Book("Nick Bostrom", "Superintelligence", 352));
bookList.add(new Book("Ray Kurzweil", "The Singularity is Near", 652));
bookList.add(new Book("Max Tegmark", "Our Mathematical Universe", 432));
Book min = Collections.min(bookList);
Book max = Collections.max(bookList);
System.out.println(String.format("Minimum element is %s", min));
System.out.println(String.format("Maximum element is %s", max));
Given our comparison criteria, the results are:
Minimum element is Book{author='Nick Bostrom', name='Superintelligence', pageNumber=352}
Maximum element is Book{author='Ray Kurzweil', name='The Singularity is Near', pageNumber=652}
Custom Comparator
You can avoid making the Book
comparable, if you can't by supplying a new Comparator
in the Collections
calls. Though, this solution is verbose and best avoided/substituted with the techniques outlined after this.
Nevertheless, it's a fully valid way to compare entities and find the maximum/minimum value:
Book min = Collections.min(bookList, new Comparator<Book>() {
@Override
public int compare(Book o1, Book o2) {
return (o1.getPageNumber() > o2.getPageNumber()) ? 1 : -1;
}
});
System.out.println(String.format("Minimum by page count is %s", min));
Or, this can be shortened through a Lambda Expression:
Book min = Collections.min(bookList,
(o1, o2) -> (o1.getPageNumber() > o2.getPageNumber()) ? 1 : -1);
Getting the Maximum or Minimum Element with Stream.max() and Stream.min()
With the advent of Java 8, we've been introduced to a wonderful Stream API that allows us to perform various processing pipelines. A Stream
can find a maximum or minimum element via the max()
or min()
methods, leveraging either a new Comparator
for the job, or using an already existing one, such as the comparator we've built-into our Book
class.
This allows you to have non-Comparable
classes and quickly use a new Comparator
to set the criteria using any field. This flexibility is what makes Streams so amazing for processing data.
Stream.max() and Stream.min() with Primitive Types
Let's start off with a simple Collection of primitive types. To get the maximum or minimum element of the collection, we stream()
it and call the min()
or max()
methods:
List<Integer> list = List.of(1, 5, 4, 3, 7, 6, 9, 4);
Integer maxInt = list.stream().mapToInt(i -> i).max().getAsInt();
Integer minInt = list.stream().mapToInt(i -> i).min().getAsInt();
System.out.println(String.format("Minimum element is %s", minInt));
System.out.println(String.format("Maximum element is %s", maxInt));
Instead of mapping i -> i
, we could've alternatively just use:
Integer maxInt = list.stream().mapToInt(Integer::intValue).max().getAsInt();
The max()
and min()
methods return an Optional
- or a derivative of the class, such as OptionalInt
, OptionalDouble
, etc. To extract the integer value - we use the getAsInt()
at the end of the call chain.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
Running this code results in:
Minimum element is 1
Maximum element is 9
Instead of mapToInt()
you can also use a Comparator
, such as comparing()
or comparingInt()
(both of which would produce the same output):
Integer maxInt = list.stream().max(Comparator.comparing(Integer::intValue)).get();
Integer minInt = list.stream().min(Comparator.comparingInt(Integer::intValue)).get();
Again, these methods return an Optional
, so we get()
the result in the end. comparing()
has the flexibility of comparing non-Integer values as well, but since we're constraining ourselves to just Integers here, it doesn't make any difference.
Stream.max() and Stream.min() with Custom Objects
Using custom comparators with custom objects is where Streams shine the most and where they provide the most flexibility. When using the Collections
framework, we were constrained to compare the elements via the compareTo()
method, overridden from the Comparable
interface, if you don't want to define a chunky new Comparator
.
With Streams - we can define a new Comparator
on the fly with any field, without the class even having to implement the Comparable
interface. Let's find the maximum and minimum element of a collection with custom objects, using a custom comparator and streams:
List<Book> bookList = new ArrayList<>();
bookList.add(new Book("Nick Bostrom", "Superintelligence", 352));
bookList.add(new Book("Ray Kurzweil", "The Singularity is Near", 652));
bookList.add(new Book("Max Tegmark", "Our Mathematical Universe", 432));
// Using native compareTo() Method
Book min = bookList.stream().min(Book::compareTo).get();
Book max = bookList.stream().max(Book::compareTo).get();
// Using custom new Comparator
Book minByAuthor = bookList.stream().min(Comparator.comparing(Book::getAuthor)).get();
Book maxByAuthor = bookList.stream().max(Comparator.comparing(Book::getAuthor)).get();
System.out.println(String.format("Minimum by page count is %s", min));
System.out.println(String.format("Maximum by page count is %s", max));
System.out.println(String.format("Minimum by author is %s", minByAuthor));
System.out.println(String.format("Maximum by author is %s", maxByAuthor));
This time around, we can use any field and supply it to the Comparator.comparing()
method. You can also use alternative methods, such as comparingInt()
here, but since we're comparing the lexicographical value of String
s, we'll be sticking with the generic comparing()
method.
Running this code results in:
Minimum by page count is Book{author='Nick Bostrom', name='Superintelligence', pageNumber=352}
Maximum by page count is Book{author='Ray Kurzweil', name='The Singularity is Near', pageNumber=652}
Minimum by author is Book{author='Max Tegmark', name='Our Mathematical Universe', pageNumber=432}
Maximum by author is Book{author='Ray Kurzweil', name='The Singularity is Near', pageNumber=652}
Using a for Loop
Finally, the good old for
loop can be used to search for a maximum or minimum element:
List<Book> bookList = new ArrayList<>();
bookList.add(new Book("Nick Bostrom", "Superintelligence", 352));
bookList.add(new Book("Ray Kurzweil", "The Singularity is Near", 652));
bookList.add(new Book("Max Tegmark", "Our Mathematical Universe", 432));
List<Integer> intList = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
// Instantiate as the first book initially
Book maxBook = bookList.get(0);
Integer maxInt = 0;
for (Book book : bookList) {
// book.getPageNumber() < minBook.getPageNumber()
if (book.getPageNumber() > maxBook.getPageNumber()) {
maxBook = book;
}
}
for (Integer integer : intList) {
// integer < minInt
if (integer > maxInt) {
maxInt = integer;
}
}
System.out.println(String.format("Maximum by page count is %s", maxBook));
System.out.println(String.format("Maximum int is %s", maxInt));
This results in the same results we've been seeing so far:
Maximum by page count is Book{author='Ray Kurzweil', name='The Singularity is Near', pageNumber=652}
Maximum int is 9
Conclusion
In this guide, we've taken a look at how to find the maximum and minimum elements of a Java Collection. We've taken a look at both primitive types and custom objects, default and custom comparators, and best practices, including a manual for
loop.