blf
blf

Reputation: 117

A cleanly overridable compareTo() method?

Having to, for the first time, define relationships amongst related objects, I found myself spending an entire weekend scouring the web for information pertaining to cleanly overridable implementations for equals() and compareTo(). Having found very little helpful information, I determined myself to find a solution. I believe the following to be a manifestation of that solution in terms of the compareTo() method. I have an idea that a similar technique might work for the equals() method as well.

My hope is that someone smarter than me might have the time to verify these findings and provide feedback regarding any pitfalls which may be encountered.

// The name chosen for the following class shell ("Base") is intended to
// portray that this compareTo() method should be implemented on a base class
// as opposed to a subclass.
public class Base
implements Comparable<Base>
{
    /**
     * Compares this Base to the specified Object for semantic ordering.
     *
     * @param other The Object to be compared.
     *
     * @return An int value representing the semantic relationship between the
     *         compared objects. The value 0 is returned when the two objects
     *         are determined to be equal (as defined by the equals method).
     *         A positive value may be returned if the "other" object is a
     *         Base and the "exact types" comparison determines this Base to
     *         have a higher semantic ordering than the "other" object, if the
     *         "other" object is not a Base, or if the "other" object is a
     *         subclass of Base who's compareTo method determines itself to
     *         have a lower semantic ordering than this Base. A negative value
     *         may be returned if the "other" object is a Base and the
     *         "exact types" comparison determines this Base to have a lower
     *         semantic ordering than the "other" object or if the "other"
     *         object is a subclass of Base who's compareTo method determines
     *         itself to have a higher semantic ordering than this Base.
     */
    public int compareTo(Base other)
    {
        int relationship = 0;

        if (other == null)
            throw new NullPointerException("other: Cannot be null.");

        if (!this.equals(other))
        {
            if (this.getClass() == Base.class)
            {
                if (this.getClass == other.getClass())
                    relationship = // Perform comparison of exact types;
                else
                    relationship = -1 * other.compareTo(this);
            }
            else
                relationship = 1;
        }

        return relationship;
    }

Upvotes: 3

Views: 327

Answers (2)

blf
blf

Reputation: 117

As stated in the topic starter, I began this project due to a lack of available information detailing the process behind the construction of a cleanly overridable compareTo() method. I hope you'll find the following to be such an implementation. Complements go to Ted Hopp for pointing me toward some key elements I had initially overlooked.

You'll find that the implementation of RelationalObject does not define an initial relationship between any elements other than those not from the same hierarchical branch, which it considers to be incomparable (and is, of course, overridable). As any relationship which this class might implement could not take into account those state elements of its deriving class, I've determined it best to leave this task solely to the user.

What this class does intend to do is make it easier to change the relational behavior for new subtypes (including those not currently implemented).

Because I expressed a desire for documentation in the topic starter, I've heavily commented each method in hopes that it might provide some guidance.

I hope someone with ample time may be willing to provide verification of these results. While I've done my best to consider all possible situations, it's always nice to gain feedback. (Positive or Negative)

public abstract class RelationalObject
implements Comparable<RelationalObject>
{
    /*
     * Compares two RelationalObjects for semantic ordering.
     *
     * @param other The RelationalObject to be compared.
     *
     * @return An int value representing the semantic relationship between the
     *         two RelationalObjects. A value of 0 is returned if the two
     *         objects are determined to be equal. A negative value is
     *         returned if "this" object is determined to have a
     *         lower semantic ordering than the "other" object. A positive
     *         value is returned if "this" object is determined to have a
     *         highter semantic ordering than the "other" object.
     */
    public final int compareTo(RelationalObject other)
    throws ClassCastException, NullPointerException
    {
        if (other == null)
            throw new NullPointerException("other: Cannot be null.");

        int relation = 0;

        if (!this.equals(other))
        {
            if (this.getClass().isAssignableFrom(other.getClass()))
            {
                if (this.getClass() == other.getClass())
                    relation = this.compareToExactType(other);
                else
                    relation = -1 * other.compareTo(this);
            }
            else
            {
                if (other.getClass().isInstance(this))
                    relation = this.compareToSuperType(other);
                else
                    relation = other.compareToForeignType(this);
            }
        }

        return relation;
    }

    /*
     * Compares two RelationalObjects with the exact same class type for
     * semantic ordering. The comparison may be based upon any of the class
     * state elements so long as the compareTo() method contract is not
     * broken.
     *
     * @param exact The RelationalObject with exactly matching type to be
     *              compared.
     *
     * @return An int value representing the semantic relationship between the
     *         two RelationalObjects.
     */
    protected abstract int compareToExactType(RelationalObject exact);

    /*
     * Compares two RelationalObjects not within the same hierarchical branch
     * for semantic ordering. The comparison may be based upon only those
     * state elements common to both objects (i.e. A comparison must be made
     * between each element and the pair's common ancestor). Should the two
     * results be equal, a ClassCastException must be thrown as the objects do
     * not contain enough distinct information to be further compared.
     *
     * @param foreign The RelationalObject from a foreign hierarchical branch
     *                to be compared.
     *
     * @return An int value representing the semantic relationship between the
     *         two RelationalObjects.
     */
    protected abstract int compareToForeignType(RelationalObject foreign);

    /*
     * Compares two RelationalObjects within the same class hierarchical
     * branch for semantic ordering. The comparison may be based upon any of
     * the class state elements so long as the compareTo() method contract is
     * not broken.
     *
     * @param upper The RelationalObject within the same heirarchical branch
     *              and with lesser definition to be compared.
     *
     * @return An int value representing the semantic relationship between the
     *         two RelationalObjects.
     */
    protected abstract int compareToSuperType(RelationalObject upper);
}

Upvotes: 0

Ted Hopp
Ted Hopp

Reputation: 234795

This will not work. Consider two classes, A and B that directly extend Base and whose instances are never equal. Then two instances Base a = new A(); and Base b = new B(). Since this.getClass() == Base.class will be false for both a and b, it will be the case that a.compareTo(b) == 1 and also that b.compareTo(a) == 1. This violates the general contract for Comparable that sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y that are comparable.

For some of the subtleties involved in comparing objects and, in particular, testing equality between subclasses, I recommend this excellent article.

Upvotes: 3

Related Questions