How do you implement compareTo methods cleanly?
-
28-10-2019 - |
Question
Currently I am writing a compareTo method for quadratic functions in the form: ax^2 + bx + c.
a, b, c are integer coefficients that are passed to the class through the constructor.
In the compareTo method, I am supposed to first compare the a-coefficients between two functions, but if they are equal, I compare the b-coefficients. If the b's are equal, I compare the c's.
The method that I came up for this ended up being pretty ugly:
public int compareTo(QuadraticFunction other)
{
if (a > other.a)
return 1;
else if (a < other.a)
return -1;
else if (b > other.b)
return 1;
else if (b < other.b)
return -1;
else if (c > other.c)
return 1;
else if (c < other.c)
return -1;
else
return 0;
}
So I was wondering, if you have these "tiered" systems of comparisons (like compare a's before b's before c's), what's the best way to implement them? I can't imagine writing a method like mine if you have to go through 10+ variables.
Solution
For an arbitrary number of coefficients (all of the same type), you should store them in a List
(or something similar), rather than individually-named member variables. That allows you to convert your example code into an iteration.
OTHER TIPS
The Guava Libraries provide an extremely nice tool to do this called ComparisonChain.
Your code would look something like this:
import com.google.common.base.ComparisonChain;
...
public int compareTo(QuadraticFunction other) {
return ComparisonChain.start()
.compare(a, other.a)
.compare(b, other.b)
.compare(c, other.c)
.result();
}
For readability, and to use the built-in compare methods for a, b, c, I would refactor to this:
public int compareTo(QuadraticFunction other) {
if (a.equals(other.a)) {
if (b.equals(other.b))
return c.compareTo(other.c);
return b.comapreTo(other.b);
}
return a.compareTo(other.a);
}
This code assumes the fields are Number
. If they are a primitive, either convert them to wrapped type or change a.equals(b) to
a == band change
a.compareTo(b)to
a - b`.
Also note that when an if
returns, there is never a need for an else
- it's redundant, so remove it.
You can use an idiom like the below which breaks the comparison into clear sections by field, only requires one test per field, and uses the signum
method to produce the return values.
Note, the subtraction below works for int
, short
, char
, or byte
fields.
For long
, float
, and double
fields you have to use separate checks for <
and ==
to avoid overflow/underflow, and loss of precision due to rounding. Be careful of NaN
too when comparing floating point values.
For Comparable
fields, you can just set delta to the result of compareTo
after using separate conditions to handle null
.
long delta = ((long) a.field1) - b.field1;
if (delta != 0) { return Long.signum(delta); }
delta = ((long) a.field2) - b.field2;
if (delta != 0) { return Long.signum(delta); }
...
return 0;