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 thanB
(other
object) - A positive number if
A
(this
object) is considered greater thanB
- 0 if
A
(this
object) is considered equal toB
(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.0
if 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
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.