Comparing based on boolean values

Imagine we have the following class which stores information about the environmental impact of a company. A shell of our class might look like

public class EnvironmentalImpact {
    private String companyName;
    private double emissions; // emissions emitted
    private int violations; // total violations committed

    // Can't just use emissions because
    // regulations could differ depending on the company
    private boolean meetsRegulations;

    // Various constructors and methods
}

It would be nice to be able to order these companies based on their environmental impact, so let’s have our class implement the Comparable interface

public class EnvironmentalImpact implements Comparable<EnvironmentalImpact> {
    private String companyName;
    private double emissions;
    private int violations;
    private boolean meetsRegulations;

    // Various constructors and methods

    public int compareTo(EnvironmentalImpact other) {
        // TODO: decide order/implement this method
    }
}

Say I want to sort the list of companies by damage to the environment, such that the most damaging ones appear first. Since I have decided this application for the EnvironmentalImpact, we must make compareTo work for it. The application of our class in Comparable situations determines how we should implement our compareTo method.

Brief review of compareTo ordering

Before we implement compareTo for our class, let’s review how compareTo determines ordering. Say we have two objects we are comparing, object A and object B, and a method call A.compareTo(B). Remember, when we implement compareTo methods in a class, object A will be referred to by the this reference, while object B will be referred to by the parameter reference (usually named other). The call A.compareTo(B) will return:

  • A negative number if A (this object) is considered less than B (other object)
  • A positive number if A (this object) is considered greater than B
  • 0 if A (this object) is considered equal to B (other object)

Back to our environmental impact example

One way we could order the companies is based on whether or not they meet the environmental regulations that apply to them. Do we want companies that meet regulations to appear earlier or later in a sorted list? Since they aren’t as damaging to the environment, we want them to appear later in the list, so we need our compareTo return to signify that companies that currently meet regulations are considered greater than companies that do not meet regulations (remember, an object considered greater than another object appears later in a sorted list).

Sometimes students get a bit tripped up when trying to compare boolean values because you can’t subtract boolean values. Subtraction should not be thought of as the way to solve compareTo problems, but more as a special trick that sometimes works for comparing based on int values. if/else structures are the more general tool for solving compareTo problems. The following would be a decent attempt at comparing based on meetsRegulations

public int compareTo(EnvironmentalImpact other) {
    if (this.meetsRegulations && !other.meetsRegulations) {
        return 1;
    } else if (!this.meetsRegulations && other.meetsRegulations)
        return -1;
    } else {
        return 0;
    }
}

If the EnvironmentalImpact object referred to by this meets regulations but the one referred to by other does not, then we want to signify that this object is greater than the other, so we return 1. And vice versa, if the other object meets regulations but this object does not, then we want to signify that this object is less than the other, so we return -1. The above two cases cover when the two objects’ meetsRegulations fields differ, so what is left is the case when both objects meet regulations and the case when neither object meets regulations. In these cases, we want to signify that the two objects are considered equal, so we return 0.

Perhaps you’re thinking that it would be nice to use a bit more of the information in our class to compare companies. Let’s add in a comparison based on the emissions of companies. We want companies to be compared as follows:

  • Companies that meet regulations should be considered greater than companies that do not meet regulations
  • If two companies either both meet regulations or both do not meet regulations, then they should be ordered based on emissions. Companies that emit more emissions should be considered less than companies that emit less emissions

Below is an example of an attempt at a compareTo implementation for the specified behavior

public int compareTo(EnvironmentalImpact other) {
    if (this.meetsRegulations != other.meetsRegulations) {
        // Note: Why don't I need to write this.meetsRegulations && !other.meetsRegulations?
        if (this.meetsRegulations)
            return 1;
        } else {
            return -1;
        }
    } else {
        return (int) (other.emissions - this.emissions);
    }
}

The check for if (this.meetsRegulations != other.meetsRegulations) is what decides whether we should compare the objects based on meeting regulations or based on emissions. If the two boolean values differ, then we want to compare based on meeting regulations.

In the case that the two objects’ meetsRegulations fields are the same, we actually have a bug. What if our emissions differ by 0.1? Casting to an int floors this value, so we end up returning 0 signifying the two objects are equal when they aren’t! Remember, we can’t use the subtraction trick when comparing double values. Below is a revised version of the method, which properly compares the emissions fields of the two objects.

public int compareTo(EnvironmentalImpact other) {
    if (this.meetsRegulations != other.meetsRegulations) {
        if (this.meetsRegulations)
            return 1;
        } else {
            return -1;
        }
    } else {
        double delta = this.emissions - other.emissions;
        if (delta < 0) {
            return 1;
        } else if (delta > 0) {
            return -1;
        } else {
            return 0;
        }
    }
}

Optional Material: Math.signum

There is a convenient method in the Math class that makes comparing by double values much easier. Math.signum takes a double and returns a double, either -1.0 if the given parameter is less than 0, 0.0if the given parameter is 0, or 1.0 if the given parameter is greater than 0. For example:

double test1 = -4.0;
double test2 = 0;
double test3 = 4.0;

Math.signum(test1); //returns -1.0
Math.signum(test2); //returns 0.0
Math.signum(test3); //returns 1.0
How could we use this when writing a compareTo method? Well instead of writing the if/else branches above, we could just write the following line of code (remembering to cast the result of Math.signum to an int)

return (int) Math.signum(other.emissions - this.emissions);

Math.signum can be a useful tool, but is by no means required when writing compareTo methods.

Recap

In summary, comparing boolean values is similar to comparing any other value type; all we need to do is remember how compareTo orders objects and then return values based on the specified ordering. An above example had the common error of trying to use the subtraction trick when comparing double values. The subtraction trick is just a special trick that can work for comparing int values. In general, it would be better to think of structuring compareTo methods based on a series of if/else branches, as that captures much more accurately the logical process that is performed when comparing objects.

More practice

Try out this problem and this other problem for more practice writing compareTo methods.