Search code examples
javacollectionscomparator

A pithy way to obtain the largest amongst a set of comparables


Context: Modern java (the latest version; JDK23 at time of writing this question)

Given 2 objects that are comparable (implement Comparable<T>, where T is a type they all share), how does one obtain the largest of the two?

Had this question been about integers, the answer is reasonably simple:

int largest = Math.max(a, b);

It's slightly annoying (but, perhaps from the perspective of varargs introducing a newly heap-allocated array, sensible due to performance concerns) that this only works for precisely 2 integers.

But there is no max for <T>. It could exist; it would be easy:

public static <T implements Comparable<? super T>> T max(T a, T b) {
  return a.compareTo(b) < 0 ? b : a;
}

But, java.lang.Math does not contain this method; only max methods from primitives.

Complications:

  • It'd be nice if you could have more than exactly 2 values, e.g. by having the signature be: <T> T max(T... inputs).
  • It'd be nice if instead of going with naturally comparable numbers, to allow specifying a custom comparator.

In that vein, I was expecting this method to exist in java.util.Comparator:

public default T max(T... inputs) {
  if (inputs.length == 0) throw new IllegalArgumentException("inputs is empty"); // [1]
  T out = inputs[0];
  for (int i = 1; i < inputs.length; i++) {
    T t = inputs[i];
    if (out.compareTo(t) < 0) out = t;
  }
  return out;
}

However, none of the classes one might expect this on (java.util.Comparator, java.util.Comparators, java.util.Comparable) appears to have such a method.

Checking some of my code projects, I have quite a few methods scattered about such as:

public LocalDate latest(LocalDate a, LocalDate b) {
   return a.isBefore(b) ? b : a;
}

which could all be replaced with such a method; LocalDate implements Comparable, so if a max method had existed I could just have called max(date1, date2) instead.

Does it exist somewhere in the java.* core libraries? Are there plans afoot in future JDK releases to have such a method?

Not asking for particular libraries, simply an inventory of sorts: Any popular libraries that do include it, in terms as generic as the snippets in this question? 2


[1] A designer might initially think returning an Optional<T> is better here; this would be the wrong design. Callers would, virtually always, pass explicit arguments, thus guaranteeing that Optional.none literally cannot happen. For the same reason it's bad API design to make a method that declares throwing a checked exception that the docs say can never occur in commonly use cases, having an API that returns an optional that can never be NONE is similarly annoying. The exception is thus the correct solution here.

[2] null handling is up for debate here. If one ascribes to the SQL view of null (which is that null represents unknown values), then an implementation that e.g. returns b if a is null would be incorrect (how do you know, between a known value and an unknown value, that the known value is the one that was larger?): An NPE is correct. But, if one ascribes to the view that null means 'missing' then NPE is wrong and returning the maximum from amongst the non-null values is right. The question is complex enough as is; let's say that any reasonable null handling is acceptable.


Solution

  • There are two very straightforward approaches already easily assembled from the Java standard library, and it's not obvious that more are necessary. All of these have alternate min versions.

    • Stream.of(inputs).max(Comparator.naturalOrder()), which accepts an arbitrary Comparator and returns an Optional, which is empty if the collection is empty. NullPointerException is thrown if null is the maximum element.
    • Collections.max(Arrays.asList(inputs)), which does correct type-safety things (to enforce that you either pass a Comparator, or have naturally Comparable elements), but returns an element or throws NoSuchElementException. null will be returned if the input contains null and the provided comparator reports that the maximum element is null.

    Since you ask about libraries, I'll mention Guava's offerings here:

    • Ordering, which was Guava's "fluent Comparator" type, has max methods accepting an Iterable, Iterator, or two arguments. Ordering isn't exactly deprecated, but is mostly obsolete at this point.
    • Comparators has a max method accepting either two Comparable arguments, or two arguments and a Comparator.