Finding max or min of exactly two objects

Tagir Valeev amaembo at gmail.com
Tue May 13 15:22:50 UTC 2025


Hello, Brian!

On Tue, May 13, 2025 at 4:30 PM Brian Goetz <brian.goetz at oracle.com> wrote:
> So with that as background, I am very cautious to consider adding methods to Comparable, because it is a highly abstract type that was designed for extension, and the risk of the above kind of clash seems "not low".

Sure, extending the Comparable interface is a no-go. Comparable
implementations in the wild are often big classes with different
semantics and a lot of functionality. This means that the chance of
signature clash is high, and even if not, the names like 'max' or
'min' may not be the best choice for the user object that happens to
implement Comparable.

> Comparator seems less risky, because it is not designed to be extended by domain objects, but instead functions more like a type class in Haskell -- it is behavior _about_ a type, defined from the outside.  And Haskell would agree with you that this move is sensible; here's Haskell's `Ord` (like Comparator), which extends `Eq` (providing equality.)

Exactly, Comparator is less risky. Usually, user-defined comparators
have nothing except a `compare` method or very few additional methods
related to comparison. The Comparator's primary job is to compare, and
finding the minimum or the maximum of two objects is pretty much
related to comparison. In contrast, the primary job of Comparable
objects is often something else, other than being compared, and
implementing comparable is just a convenience, which is rarely
necessary.

> OK, "comparative languages" lesson over, back to your point.  There are two ways to get where you want: a static method that takes a comparator and the operands (`Comparator.max(c, a, b)`), or a default method on Comparator (`c.max(a, b)`).  (You say "add simple static methods ... to Comparator" but I think you mean to put the word `static` elsewhere in that sentence.)

Well, probably I formulated it not so clearly. I'd like to have the
following additions:

public interface Comparator<T> {
...
    default T max(T a, T b) {
        return compare(a, b) > 0 ? a : b;
    }

    default T min(T a, T b) {
        return compare(a, b) > 0 ? b : a;
    }

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

    static <T extends Comparable<T>> T min(T a, T b) {
        return a.compareTo(b) > 0 ? b : a;
    }
}

The static methods are not exactly related to the Comparator
interface. That's why I'm somewhat dubious about the proper place for
them (probably j.u.Objects fits better). They are just convenient
shortcuts for Comparator.naturalOrder().max(...) and
Comparator.naturalOrder().min(...) (and having one indirection less,
which sometimes might be helpful for performance). We can omit static
methods, keeping only default ones, so users will need to write fully
Comparator.naturalOrder().max(...). This is still better than what we
have today, but I think that natural order is an important enough
concept to have dedicated methods for it.

Passing a comparator as an argument to a static method (like
Comparator.max(c, a, b)) is possible and somewhat in-line with
existing APIs like Collections.max(coll[, comp]). However, I feel that
adding default method is better.

With best regards,
Tagir Valeev


>
> I am receptive to the idea of extending Comparator here, but would want to think about it more to feel out potential mistakes like the `andThen` one above.  But your point is solid: a "comparator" is also a "maxxer" and a "minner" (neither of those are words, and if they were, are probably spelled wrong), and that is a natural place to locate such behavior.
>
>
>
>
> On 5/13/2025 10:12 AM, Tagir Valeev wrote:
>
> Hello!
>
> Several times already when writing Java programs, I stumbled with a simple task: given two objects with natural order, find the maximal of them. The algorithm I need could be formulated like this:
>
>     <T extends Comparable<T>> T max(T a, T b) {
>         return a.compareTo(b) > 0 ? a : b;
>     }
>
> Writing manually compareTo >= 0 looks too verbose, not very readable and error-prone: one has to mention both objects twice, and it's possible to mix > with <. I can surely add a method mentioned above to a utility class in my project and use it everywhere. However, I feel that it deserves a place in the standard library.
>
> The alternatives we have now:
> BinaryOperator.maxBy(Comparator.<T>naturalOrder()).apply(a, b);
> This speaks clearly about the intent (we'd like to get the maximum and we write 'maxBy') but very wordy.
>
> Stream.of(a, b).max(Comparator.naturalOrder()).get();
> Also clear and a little bit shorter, but has an unnecessary Optional in-between (we know that we have at least one element, so the result is always present) and we have to mention the comparator. Finally, it might be much less efficient than expected.
>
> Probably we can add simple static methods `max` and `min` either to the `Comparator` interface, or to `java.util.Objects`? Such methods would complement methods from the `Math` class for numbers. In addition, having default methods `max` and `min` in the `Comparator` interface would also be nice:
>
> String bigger = String.CASE_INSENSITIVE_ORDER.max("Hello", "world");
>
> What do you think? Can we proceed with such an enhancement?
>
> With best regards,
> Tagir Valeev
>
>


More information about the core-libs-dev mailing list