Java error: "Comparison method violates its general contract!" - java

I have this code:
package org.optimization.geneticAlgorithm;
import org.optimization.geneticAlgorithm.selection.Pair;
public abstract class Chromosome implements Comparable<Chromosome> {
public abstract double fitness();
public abstract Pair<Chromosome> crossover(Chromosome parent);
public abstract void mutation();
public int compareTo(Chromosome o) {
int rv = 0;
if (this.fitness() > o.fitness()) {
rv = -1;
} else if (this.fitness() < o.fitness()) {
rv = 1;
}
return rv;
}
}
And every time I run this code I get this error:
Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.ComparableTimSort.mergeHi(ComparableTimSort.java:835)
at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:453)
at java.util.ComparableTimSort.mergeCollapse(ComparableTimSort.java:376)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:182)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
at java.util.Arrays.sort(Arrays.java:472)
at java.util.Collections.sort(Collections.java:155)
at org.optimization.geneticAlgorithm.GeneticAlgorithm.nextGeneration(GeneticAlgorithm.java:74)
at org.optimization.geneticAlgorithm.GeneticAlgorithm.execute(GeneticAlgorithm.java:40)
at test.newData.InferenceModel.main(InferenceModel.java:134)
I use OpenJDK7u3 and I return 0 when the objects are equal. Can someone explain this error to me?

You could get into that situation if you have any NaN values:
For example:
public class Test
{
public static void main(String[] args) {
double a = Double.NaN;
double b = Double.NaN;
double c = 5;
System.out.println(a < b);
System.out.println(a > b);
System.out.println(b < c);
System.out.println(c < b);
}
}
All of these print false. So you could end up in a situation where two non-NaN values were both deemed "equal" to NaN, but one was greater than the other. Basically, you should work out how you want to handle NaN values. Also check that that really is the problem, of course... do you really want NaN values for your fitness?

Most probably your fitness function is broken, in one of two ways:
It doesn't always return the same value when called on the same object.
It could return NaNs. Your compareTo() is not transitive in the presence of NaNs, as explained by Jon Skeet.
You could rewrite your comparison function using Double.compare():
public int compareTo(Chromosome o) {
return Double.compare(o.fitness(), this.fitness());
}
This requires less code and takes care of corner cases (NaNs, the negative zero etc). Of course, whether these corner cases should be arising in the first place is for you to decide and address.

You should try adding if (this == o) return 0;
Because the same object must be returned equal.

Related

Java Faster Equals method For float type

As I know set.add use equals method of FastFloat
For me important only first two digits after point(!!!), so in the equals method to make equals faster I use Math.abs() >= 0.001 but I don't understand why this code return 2 instead of 1 because Math.abs(3.54 - 3.5405) < 0.001
Code:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class st {
public class FastFloat {
private float ff;
public FastFloat(float ff) {
super();
this.ff = ff;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + Float.floatToIntBits(ff);
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof FastFloat))
return false;
FastFloat other = (FastFloat) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
/* if (Float.floatToIntBits(ff) != Float.floatToIntBits(other.ff))
return false;*/
if (Math.abs(ff - other.ff) >= 0.001)
return false;
return true;
}
private st getOuterType() {
return st.this;
}
}
public static void main(String[] args) {
List<Set<FastFloat>> lsff = new ArrayList<>();
lsff.add(0, new HashSet<>());
Set<FastFloat> sff = lsff.get(0);
st x = new st();
sff.add(x.new FastFloat((float)3.54));
sff.add(x.new FastFloat((float)3.5405));
System.out.println(lsff.get(0).size());
}
}
Your hashCode() method gives different results for ff==3.54 and ff==3.5405, so they are assigned to different buckets of the HashSet and your equals method is never used to test if they are equal.
If a.equals(b), a.hashCode() must be equal to b.hashCode(). That's the contract.
From the equals() Javadoc :
Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.
The type of almost-equals you are implementing does not meet Object's contract for equals, because it is not transitive.
Consider the case of 0, 0.0009, and 0.0018. Math.abs(0.0009 - 0) is less than 0.001, as is Math.abs(0.0018 - 0.009), but Math.abs(0.0018 - 0) is greater than 0.001.
It is fine to have an almost-equals method, and use it in some contexts, but to make HashSet, and other hashing structures work, you need your equals and hashCode to conform to the Object contract.
If the only reason is "speed", get rid of the complications and use Float.
If you have some other reason for needing to group unequal floats together in your HashSet, there are many ways to divide them into equivalence classes, including the one discussed in k5_'s answer. You can also base it on pure floating point arithmetic, without having to extract and manipulate bit patterns. For example, base it on division by 0.001:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class st {
public class FastFloat {
private float ff;
private float fudgeFactor = 0.001f;
private float fudged;
public FastFloat(float ff) {
super();
this.ff = ff;
this.fudged = (float) Math.rint(ff / fudgeFactor);
}
#Override
public int hashCode() {
return Float.hashCode(fudged);
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof FastFloat))
return false;
FastFloat other = (FastFloat) obj;
return Float.compare(this.fudged, other.fudged) == 0;
}
private st getOuterType() {
return st.this;
}
}
public static void main(String[] args) {
List<Set<FastFloat>> lsff = new ArrayList<>();
lsff.add(0, new HashSet<>());
Set<FastFloat> sff = lsff.get(0);
st x = new st();
sff.add(x.new FastFloat((float) 3.54));
sff.add(x.new FastFloat((float) 3.5405));
System.out.println(lsff.get(0).size());
sff.add(x.new FastFloat(0f));
System.out.println(lsff.get(0).size());
}
}
If you want to implement .equals()/.hashCode() for float with some floats being equal. So you can use your class with HashSet or HashMap.
One way of doing that is to strip the last <23 bits (as you remove binary places not decimal, to keep 4 valid decimal places thats about 10 binary places removed) from the Float.floatToInBits(). You use the resulting int in your .equals() and .hashCode() implementation.
I didnt do the math in binary so the numbers are not actually correct (examples if you do it in decimal)
Something like 1000000 will be equal to 1000555 but will be different to 999999
Something like 1,00101 will be equal to 1,0013 but different to 1,00099
This approach leads to a valid .equals()/.hashCode() but doesnt provide the Math.abs() < 0.001 you wanted (in same cases the abs will be much(!) higher and in some cases lower). If that property is more important to you dont use .equals()/.hashCode() and create your own collections.

Scaling method fails to satisfy JUnit

I’m trying to make a position, length and circle classes based on given JUnit in order to eventually output them graphically. But I’m stuck in one of the methods for days now.
I tried to truncate precisions but then my equals method failed.
JUnit for Scale:
public void testScale(){
Length inch2 = Length.unit.scale(320.0);
assertTrue(inch2 != null);
assertEquals(Length.inch,inch2);
assertFalse(inch2.equals(Length.unit));
Length unit2 = Length.cm.scale(1.0/125.98425197);
assertTrue(unit2 != null);
assertEquals(Length.unit,unit2); // This is the line my scale method fails
// Here my unit2 has a length of 1.0001249999881234
// and my constant cm has a length of 1.0 but
// truncating those precisions caused my equals
// method to fails.
assertFalse(unit2.equals(Length.cm));
Length z = Length.meter.scale(0);
assertTrue(z != null);
assertEquals(Length.zero,z);
assertFalse(z.equals(Length.meter));
assertFalse(Length.zero.equals(null));
}
My scale method:
public Length scale(double d) {
if (d < 0)
throw new IllegalArgumentException();
else {
return new Length(d* this.length);
}
}
I suspect maybe the problem is coming from my equals method but in the given JUnit it is passing the tests.
JUnit for Equals:
public void testEquals(){
assertFalse(Length.unit.equals("Not a length"));
assertFalse(Length.inch.equals(null));
assertEquals(Length.zero,Length.unit.scale(0.0000001));
assertTrue(Length.unit.scale(0.0000001).compareTo(Length.zero) == 0);
assertTrue(Length.zero.compareTo(Length.unit.scale(0.0000001)) == 0);
assertFalse(Length.unit.scale(0.0000015).equals(Length.zero));
assertTrue(Length.unit.scale(0.0000015).compareTo(Length.zero) > 0);
assertTrue(Length.zero.compareTo(Length.unit.scale(0.0000015)) < 0);
}
My Equals Method:
#Override
public boolean equals(Object other) {
if (other == null || !(other instanceof Length)) {
return false;
}
Length o = (Length) other;
if (Math.abs(this.length - o.length) < 0.000001) {
return true;
} else {
return false;
}
}
Please help
Link for all my code:
https://www.dropbox.com/sh/bz400f8y0ufx381/59aUTilrBt
You are testing too many things at once.
A unit test should be one unit of code - one aspect of the code as opposed to everything at once.
I also notice that you don't have any of your test methods annotated with #Test; you should be doing this with JUnit4 tests.
So, for your first test, you have a relatively small scale method you want to exercise. Let's enumerate the cases:
d < 0. I should expect an IllegalArgumentException.
d >= 0. I should expect a new instance of Length with a size some multiple of d and whatever the set length of the instance is.
What this looks like is two discrete tests:
#Test(expected = IllegalArgumentException.class)
public void scaleShouldThrowExceptionWhenInvalidLength() {
}
#Test
public void scaleShouldBehaveNormally() {
}
I leave you to fill in the blanks, since I don't know what object scale is attached to.
Equals is the same way - you want to exercise each condition of the equivalence.
By the way, you can do return Math.abs(this.length - o.length) < 0.000001 for your conditions. return true and return false scream bad practice.
The object you're passing in is null.
The object you're passing in is not an instance of Length.
The object you're passing in fails Math.abs(this.length - o.length) < 0.000001.
The object you're passing in passes Math.abs(this.length - o.length) < 0.000001.
So the above are four discrete tests.
#Test
public void equalsShouldFailIfNull() {
}
#Test
public void equalsShouldFailIfNotInstanceOfLength() {
}
#Test
public void equalsDoesNotMeetCondition() {
}
#Test
public void equalsMeetsCondition() {
}
Filling in the blanks, I leave as an exercise to the reader.
Be very careful when dealing with floating-point numbers. You won't always get an exact representation back (that is, you may get an imprecise value when dealing with fractions). Be certain that your equals method is well-defined to respect what could happen when you don't have an exact decimal value to work with.
Alternatively, if you really need the decimal precision, use a BigDecimal instead.

what would be a good hash function for an integer tuple?

I have this class...
public class StartStopTouple {
public int iStart;
public int iStop;
public int iHashCode;
public StartStopTouple(String start, String stop) {
this.iStart = Integer.parseInt(start);
this.iStop = Integer.parseInt(stop);
}
#Override
public boolean equals(Object theObject) {
// check if 'theObject' is null
if (theObject == null) {
return false;
}
// check if 'theObject' is a reference to 'this' StartStopTouple... essentially they are the same Object
if (this == theObject) {
return true;
}
// check if 'theObject' is of the correct type as 'this' StartStopTouple
if (!(theObject instanceof StartStopTouple)) {
return false;
}
// cast 'theObject' to the correct type: StartStopTouple
StartStopTouple theSST = (StartStopTouple) theObject;
// check if the (start,stop) pairs match, then the 'theObject' is equal to 'this' Object
if (this.iStart == theSST.iStart && this.iStop == theSST.iStop) {
return true;
} else {
return false;
}
} // equal() end
#Override
public int hashCode() {
return iHashCode;
}
}
... and I define equality between such Objects only if iStart and iStop in one Object are equal to iStart and iStop in the other Object.
So since I've overridden equals(), I need to override hashCode() but I'm not sure how to define a good hash function for this class. What would be a good way to create a hash code for this class using iStart and iStop?
I'd be tempted to use this, particularly since you're going to memoize it:
Long.valueOf((((long) iStart) << 32) | istop)).hashcode();
From Bloch's "Effective Java":
int iHashCode = 17;
iHashCode = 31 * iHashCode + iStart;
iHashCode = 31 * iHashCode + iStop;
Note: 31 is chosen because the multiplication by 31 can be optimized by the VM as bit operations. (But performance is not useful in your case since as mentioned by #Ted Hopp you are only computing the value once.)
Note: it does not matter if iHashCode rolls over past the largest int.
the simplest might be best
iHashCode = iStart^iStop;
the XOR of the two values
note this will give equal hashcodes when start and stop are swapped
as another possibility you can do
iHashCode = ((iStart<<16)|(iStart>>>16))^iStop;
this first barrel shifts start by 16 and then xors stop with it so the least significant bits are put apart in the xor (if start is never larger than 65k (of more accurately 2^16) you can omit the (iStart>>>16) part)

Treeset.contains() problem

So I've been struggling with a problem for a while now, figured I might as well ask for help here.
I'm adding Ticket objects to a TreeSet, Ticket implements Comparable and has overridden equals(), hashCode() and CompareTo() methods. I need to check if an object is already in the TreeSet using contains(). Now after adding 2 elements to the set it all checks out fine, yet after adding a third it gets messed up.
running this little piece of code after adding a third element to the TreeSet, Ticket temp2 is the object I'm checking for(verkoopLijst).
Ticket temp2 = new Ticket(boeking, TicketType.STANDAARD, 1,1);
System.out.println(verkoop.getVerkoopLijst().first().hashCode());
System.out.println(temp2.hashCode());
System.out.println(verkoop.getVerkoopLijst().first().equals(temp2));
System.out.println(verkoop.getVerkoopLijst().first().compareTo(temp2));
System.out.println(verkoop.getVerkoopLijst().contains(temp2));
returns this:
22106622
22106622
true
0
false
Now my question would be how this is even possible?
Edit:
public class Ticket implements Comparable{
private int rijNr, stoelNr;
private TicketType ticketType;
private Boeking boeking;
public Ticket(Boeking boeking, TicketType ticketType, int rijNr, int stoelNr){
//setters
}
#Override
public int hashCode(){
return boeking.getBoekingDatum().hashCode();
}
#Override
#SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
public boolean equals(Object o){
Ticket t = (Ticket) o;
if(this.boeking.equals(t.getBoeking())
&&
this.rijNr == t.getRijNr() && this.stoelNr == t.getStoelNr()
&&
this.ticketType.equals(t.getTicketType()))
{
return true;
}
else return false;
}
/*I adjusted compareTo this way because I need to make sure there are no duplicate Tickets in my treeset. Treeset seems to call CompareTo() to check for equality before adding an object to the set, instead of equals().
*/
#Override
public int compareTo(Object o) {
int output = 0;
if (boeking.compareTo(((Ticket) o).getBoeking())==0)
{
if(this.equals(o))
{
return output;
}
else return 1;
}
else output = boeking.compareTo(((Ticket) o).getBoeking());
return output;
}
//Getters & Setters
On compareTo contract
The problem is in your compareTo. Here's an excerpt from the documentation:
Implementor must ensure sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y.
Your original code is reproduced here for reference:
// original compareTo implementation with bug marked
#Override
public int compareTo(Object o) {
int output = 0;
if (boeking.compareTo(((Ticket) o).getBoeking())==0)
{
if(this.equals(o))
{
return output;
}
else return 1; // BUG!!!! See explanation below!
}
else output = boeking.compareTo(((Ticket) o).getBoeking());
return output;
}
Why is the return 1; a bug? Consider the following scenario:
Given Ticket t1, t2
Given t1.boeking.compareTo(t2.boeking) == 0
Given t1.equals(t2) return false
Now we have both of the following:
t1.compareTo(t2) returns 1
t2.compareTo(t1) returns 1
That last consequence is a violation of the compareTo contract.
Fixing the problem
First and foremost, you should have taken advantage of the fact that Comparable<T> is a parameterizable generic type. That is, instead of:
// original declaration; uses raw type!
public class Ticket implements Comparable
it'd be much more appropriate to instead declare something like this:
// improved declaration! uses parameterized Comparable<T>
public class Ticket implements Comparable<Ticket>
Now we can write our compareTo(Ticket) (no longer compareTo(Object)). There are many ways to rewrite this, but here's a rather simplistic one that works:
#Override public int compareTo(Ticket t) {
int v;
v = this.boeking.compareTo(t.boeking);
if (v != 0) return v;
v = compareInt(this.rijNr, t.rijNr);
if (v != 0) return v;
v = compareInt(this.stoelNr, t.stoelNr);
if (v != 0) return v;
v = compareInt(this.ticketType, t.ticketType);
if (v != 0) return v;
return 0;
}
private static int compareInt(int i1, int i2) {
if (i1 < i2) {
return -1;
} else if (i1 > i2) {
return +1;
} else {
return 0;
}
}
Now we can also define equals(Object) in terms of compareTo(Ticket) instead of the other way around:
#Override public boolean equals(Object o) {
return (o instanceof Ticket) && (this.compareTo((Ticket) o) == 0);
}
Note the structure of the compareTo: it has multiple return statements, but in fact, the flow of logic is quite readable. Note also how the priority of the sorting criteria is explicit, and easily reorderable should you have different priorities in mind.
Related questions
What is a raw type and why shouldn't we use it?
How to sort an array or ArrayList ASC first by x and then by y?
Should a function have only one return statement?
This could happen if your compareTo method isn't consistent. I.e. if a.compareTo(b) > 0, then b.compareTo(a) must be < 0. And if a.compareTo(b) > 0 and b.compareTo(c) > 0, then a.compareTo(c) must be > 0. If those aren't true, TreeSet can get all confused.
Firstly, if you are using a TreeSet, the actual behavior of your hashCode methods won't affect the results. TreeSet does not rely on hashing.
Really we need to see more code; e.g. the actual implementations of the equals and compareTo methods, and the code that instantiates the TreeSet.
However, if I was to guess, it would be that you have overloaded the equals method by declaring it with the signature boolean equals(Ticket other). That would lead to the behavior that you are seeing. To get the required behavior, you must override the method; e.g.
#Override
public boolean equals(Object other) { ...
(It is a good idea to put in the #Override annotation to make it clear that the method overrides a method in the superclass, or implements a method in an interface. If your method isn't actually an override, then you'll get a compilation error ... which would be a good thing.)
EDIT
Based on the code that you have added to the question, the problem is not overload vs override. (As I said, I was only guessing ...)
It is most likely that the compareTo and equals are incorrect. It is still not entirely clear exactly where the bug is because the semantics of both methods depends on the compareTo and equals methods of the Boeking class.
The first if statement of the Ticket.compareTo looks highly suspicious. It looks like the return 1; could cause t1.compareTo(t2) and t2.compareTo(t1) to both return 1 for some tickets t1 and t2 ... and that would definitely be wrong.

How can I compare null values using Comparator?

I've got a few Comparators -- one for Dates, one for decimals, one for percentages, etc.
At first my decimal comparator looked like this:
class NumericComparator implements Comparator<String> {
#Override
public int compare(String s1, String s2) {
final Double i1 = Double.parseDouble(s1);
final Double i2 = Double.parseDouble(s2);
return i1.compareTo(i2);
}
}
Life was simple. Of course, this doesn't handle the case where the strings aren't parseable. So I improved compare():
class NumericComparator implements Comparator<String> {
#Override
public int compare(String s1, String s2) {
final Double i1;
final Double i2;
try {
i1 = Double.parseDouble(s1);
} catch (NumberFormatException e) {
try {
i2 = Double.parseDouble(s2);
} catch (NumberFormatException e2) {
return 0;
}
return -1;
}
try {
i2 = Double.parseDouble(s2);
} catch (NumberFormatException e) {
return 1;
}
return i1.compareTo(i2);
}
}
Life was better. Tests felt more solid. However, my code reviewer pointed out, "What about nulls?"
Great, so now I have to repeat the above with NullPointerException or prepend the method body with:
if (s1 == null) {
if (s2 == null) {
return 0;
} else {
return -1;
}
} else if (s2 == null) {
return 1;
}
This method is huge. The worst part is, I need to repeat this pattern with three other classes which compare different types of strings and could raise three other exceptions while parsing.
I'm not a Java expert. Is there a cleaner, neater solution than -- gasp -- copying and pasting? Should I trade correctness for lack of complexity so as long as it is documented?
Update: Some have suggested that it's not the Comparator's job to handle null values. Since the sort results are displayed to users I indeed want nulls to be sorted consistently.
You are implementing a Comparator<String>. String's methods, including compareTo throw a NullPointerException if a null is handed in to them, so you should too. Similarly, Comparator throws a ClassCastException if the arguments' types prevent them from being compared. I would recommend you implement these inherited behaviors.
class NumericComparator implements Comparator<String> {
public int compare(String s1, String s2) {
final Double i1;
final Double i2;
if(s1 == null)
{
throw new NullPointerException("s1 is null"); // String behavior
}
try {
i1 = Double.parseDouble(s1)
} catch (NumberFormatException e) {
throw new ClassCastException("s1 incorrect format"); // Comparator behavior
}
if(s2 == null)
{
throw new NullPointerException("s2 is null"); // String behavior
}
try {
i2 = Double.parseDouble(s1)
} catch (NumberFormatException e) {
throw new ClassCastException("s2 incorrect format"); // Comparator behavior
}
return i1.compareTo(i2);
}
}
You can almost regain the original elegance by extracting a method to do the type checking and conversion.
class NumericComparator implements Comparator<String> {
public int compare(String s1, String s2) {
final Double i1;
final Double i2;
i1 = parseStringAsDouble(s1, "s1");
i2 = parseStringAsDouble(s2, "s2");
return i1.compareTo(i2);
}
private double parseStringAsDouble(String s, String name) {
Double i;
if(s == null) {
throw new NullPointerException(name + " is null"); // String behavior
}
try {
i = Double.parseDouble(s1)
} catch (NumberFormatException e) {
throw new ClassCastException(name + " incorrect format"); // Comparator behavior
}
return i;
}
}
If you are not particular about the Exception messages, you can lose the "name" parameter. I'm sure you can lose an extra line here or word there by applying little tricks.
You say you need to repeat this pattern with three other classes which compare different types of strings and could raise three other exceptions. It's difficult to offer specifics there without seeing the situation, but you may be able to use "Pull Up Method" on a version of my parseStringAsDouble into a common ancestor of NumericComparator that itself implements java's Comparator.
There are a lot of subjective answers to this question. Here's my own $.02.
First, the trouble you're describing is the canonical symptom of a language that lacks first-class functions, which would enable you to succinctly describe these patterns.
Second, in my opinion, it should be an error to compare two Strings as Doubles if one of them cannot be considered a representation of a double. (The same is true for nulls, etc.) Therefore, you should permit the exceptions to propagate! This will be a contentious opinion, I expect.
Here's how I'd improve the comparator:
First, exctract a method for converting the value. It's being repeated, multiple try...catches are always ugly -> better to have as few of them as possible.
private Double getDouble(String number) {
try {
return Double.parseDouble(number);
} catch(NumberFormatException e) {
return null;
}
}
Next, write down simple rules to show how you want the flow of the comparator to be.
if i1==null && i2!=null return -1
if i1==null && i2==null return 0
if i1!=null && i2==null return 1
if i1!=null && i2!=null return comparison
Finally do horrible obfuscation to the actual comparator to raise a few WTF:s in code review (or like others like to say it, "Implement the Comparator"):
class NumericComparator implements Comparator<String> {
public int compare(String s1, String s2) {
final Double i1 = getDouble(s1);
final Double i2 = getDouble(s2);
return (i1 == null) ? (i2 == null) ? 0 : -1 : (i2 == null) ? 1 : i1.compareTo(i2);
}
private Double getDouble(String number) {
try {
return Double.parseDouble(number);
} catch(NumberFormatException e) {
return null;
}
}
}
...yes, that's a branching nested ternary. If anyone complains about it, say what others here have been saying: Handling nulls isn't Comparator's job.
You could create a utility method that handles parsing and returns a certain value in the case of nulls or parse exceptions.
Take a step back. Where does those Strings come from? For what is this Comparator to be used? Do you have a Collection of Strings which you would like to sort or so?
Try this:
import com.google.common.base.Function;
import com.google.common.collect.Ordering;
Ordering.nullsFirst().onResultOf(
new Function<String, Double>() {
public Double apply(String s) {
try {
return Double.parseDouble(s);
} catch (NumberFormatException e) {
return null;
}
})
The only problem, if it you consider it that, is that null Strings and other non-parseable Strings will all be intermingled. That's probably not a big deal, considering the benefits -- this gives you a comparator that is guaranteed to be correct, whereas with a hand-coded comparator, even relatively simple ones, it's amazing how easy it is to commit a subtle error that breaks transitivity or, umm, antisymmetricity.
http://google-collections.googlecode.com
It seems that there are two concerns being mixed here and maybe should be broken up into separate components. Consider the following:
public class ParsingComparator implements Comparator<String> {
private Parser parser;
public int compare(String s1, String s2) {
Object c1 = parser.parse(s1);
Object c2 = parser.parse(s2);
new CompareToBuilder().append(c1, c2).toComparison();
}
}
The Parser interface would have implementations for numbers, dates, etc. You could potentially use the java.text.Format class for your Parser interface. If you don't want to use commons-lang, you could replace the use of CompareToBuilder with some logic to handle nulls and use Comparable instead of Object for c1 and c2.
tl;dr: Take guidance from the JDK. The Double comparator is not defined for either non-numbers or nulls. Make people give you useful data (Doubles, Dates, Dinosaurs, whatever) and write your comparators for that.
As near as I can tell, this is a case of user input validation. For example, if you are taking input from a dialog box, the correct place to ensure that you have a parseable String that is a Double, Date or whatever is in the input handler. Make sure it's good before the user can tab away, hit "Okay" or equivalent.
Here's why I think this:
First question: if the Strings aren't parseable as numbers, I think you're trying to solve the problem in the wrong place. Say, for instance, I try to compare "1.0" to "Two". The second is clearly not parseable as a Double but is it less than the first? Or is it greater. I would argue that the users should have to turn their Strings into Doubles before they ask your which is greater (which you can easily answer with Double.compareTo, for instance).
Second question: if the Strings are "1.0" and null, which is greater? The JDK source doesn't handle NullPointerExceptions in the Comparator: if you give it a null, autoboxing will fail.
The worst part is, I need to repeat
this pattern with three other classes
which compare different types of
strings and could raise three other
exceptions while parsing.
Exactly why I would argue that the parsing should happen outside your Comparator with exception-handling dealt with before it arrives at your code.
If you are able to change the signature I would suggest you write the method so that it can accept any supported Object.
public int compare(Object o1, Object o2) throws ClassNotFoundException {
String[] supportedClasses = {"String", "Double", "Integer"};
String j = "java.lang.";
for(String s : supportedClasses){
if(Class.forName(j+s).isInstance(o1) && Class.forName(j+s).isInstance(o1)){
// compare apples to apples
return ((Comparable)o1).compareTo((Comparable)o2);
}
}
throw new ClassNotFoundException("Not a supported Class");
}
You might even define it recursively where you cast your Strings to Doubles and then return the result of calling itself with those objects.
IMHO you should first create a method that returns a Double from a String, embedding the null and parsing failure cases (but you must define what to do in such cases : throw an exception ? return a default value ??).
Then your comparator just have to compare obtained Double instances.
In other words, refactoring...
But I still wonder why you need to compare strings though expecting they represent doubles. I mean, what prevents you from manipulating doubles in the code that would actually use this comparator ?
according to your needs and Ewan's post, I think there's a way to extract the structure that you can reuse:
class NumericComparator implements Comparator<String> {
private SafeAdaptor<Double> doubleAdaptor = new SafeAdaptor<Double>(){
public Double parse(String s) {
return Double.parseDouble(s);
}
};
public int compare(String s1, String s2) {
final Double i1 =doubleAdaptor.getValue(s1, "s1");
final Double i2 = doubleAdaptor.getValue(s2, "s2");
return i1.compareTo(i2);
}
}
abstract class SafeAdaptor<T>{
public abstract T parse(String s);
public T getValue(String str, String name) {
T i;
if (str == null) {
throw new NullPointerException(name + " is null"); // String
}
try {
i = parse(str);
} catch (NumberFormatException e) {
throw new ClassCastException(name + " incorrect format"); // Comparator
}
return i;
}
}
I extract the method as an abstract class which can be reuse in other cases(although the class name is suck).
cheers.
So I improved compare()...
sure you did.
first, the Comparator interface doesn't specify what happens with nulls. if your null checking if statement works for your use case, that's great, but the general solution is throwing an npe.
as to cleaner... why final? why all the catch/throws? why use compareTo for a primitive wrapper?
class NumericComparator implements Comparator<String> {
public int compare(String s1, String s2) throws NullPointerException, NumberFormatException {
double test = Double.parseDouble(s1) - Double.parseDouble(s2);
int retVal = 0;
if (test < 0) retVal = -1;
else if (test > 0) retVal = 1;
return retVal;
}
}
seems you might find it clearer renaming test to t1 and retVal to q.
as to repeating the pattern... eh. you might be able to use generics with reflection to invoke appropriate parseX methods. seems like that'd not be worth it though.

Categories

Resources