I'm implementing a value object for these interfaces:
interface FooConsumer
{
public void setFoo(FooKey key, Foo foo);
public Foo getFoo(FooKey key);
}
// intent is for this to be a value object with equivalence based on
// name and serial number
interface FooKey
{
public String getName();
public int getSerialNumber();
}
and from what I've read (e.g. in Enforce "equals" in an interface and toString(), equals(), and hashCode() in an interface) it looks like the recommendation is to provide an abstract base class, e.g.
abstract class AbstractFooKey
{
final private String name;
final private int serialNumber
public AbstractFooKey(String name, int serialNumber)
{
if (name == null)
throw new NullPointerException("name must not be null");
this.name = name;
this.serialNumber = serialNumber;
}
#Override public boolean equals(Object other)
{
if (other == this)
return true;
if (!(other instanceof FooKey))
return false;
return getName().equals(other.getName()
&& getSerialNumber() == other.getSerialNumber()
&& hashCode() == other.hashCode(); // ***
}
#Override public int hashCode()
{
return getName().hashCode() + getSerialNumber()*37;
}
}
My question is about the last bit I added here, and how to deal with the situation where AbstractFooKey.equals(x) is called with a value for x that is an instance of a class that implements FooKey but does not subclass AbstractFooKey. I'm not sure how to handle this; on the one hand I feel like the semantics of equality should just depend on the name and serialNumber being equal, but it appears like the hashCodes have to be equal as well in order to satisfy the contract for Object.equals().
Should I be:
really lax and just forget about the line marked ***
lax and keep what I have
return false from equals() if the other object is not an AbstractFooKey
be really strict and get rid of the interface FooKey and replace it with a class that is final?
something else?
Document the required semantics as part of the contract.
Ideally you'd actually have a single implementation which is final, which kind of negates the need of an interface for this particular purpose. You may have other reasons for wanting an interface for the type.
The contract requirements of Object is actually from hashCode: If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
You don't need to include hashCode in the equals computation, rather you need to include all properties involved in equals in the hashCode calculation. In this case I'd simply compare serialNumber and name in both equals and hashCode.
Keep it simple unless you have a real reason to complicate it.
Start with a final, immutable class.
If you need an interface, create one to match, and document the semantics and default implementation.
For the equals and hashmap, there are strict contracts:
Reflexive - It simply means that the object must be equal to itself, which it would be at any given instance; unless you intentionally override the equals method to behave otherwise.
Symmetric - It means that if object of one class is equal to another class object, the other class object must be equal to this class object. In other words, one object can not unilaterally decide whether it is equal to another object; two objects, and consequently the classes to which they belong, must bilaterally decide if they are equal or not. They BOTH must agree.
Transitive - It means that if the first object is equal to the second object and the second object is equal to the third object; then the first object is equal to the third object. In other words, if two objects agree that they are equal, and follow the symmetry principle, one of them can not decide to have a similar contract with another object of different class. All three must agree and follow symmetry principle for various permutations of these three classes.
Consistent - It means that if two objects are equal, they must remain equal as long as they are not modified. Likewise, if they are not equal, they must remain non-equal as long as they are not modified. The modification may take place in any one of them or in both of them.
null comparison - It means that any instantiable class object is not equal to null, hence the equals method must return false if a null is passed to it as an argument. You have to ensure that your implementation of the equals method returns false if a null is passed to it as an argument.
Contract for hashCode():
Consistency during same execution - Firstly, it states that the hash code returned by the hashCode method must be consistently the same for multiple invocations during the same execution of the application as long as the object is not modified to affect the equals method.
Hash Code & Equals relationship - The second requirement of the contract is the hashCode counterpart of the requirement specified by the equals method. It simply emphasizes the same relationship - equal objects must produce the same hash code. However, the third point elaborates that unequal objects need not produce distinct hash codes.
(From: Technofundo: Equals and Hash Code)
However, using instanceof in equals is not the right thing to do. Joshua Bloch detailed this in Effective Java, and your concerns regarding the validity of your equals implementation is valid. Most likely, problems arising from using instanceof are going to violate the transitivity part in the contract when used in connection with descendants of the base class - unless the equals function is made final.
(Detailed a bit better than I could ever do here: Stackoverflow: Any reason to prefer getClass() over instanceof when generating .equals()?)
Also read:
Java API equals contract
Java API hashCode contract
If the equality of a FooKey is such that two FooKeys with the same name and serial numbers are considered to be equal then you can remove the line in the equals() clause that compares the hashcodes.
Or you could leave it in, it does not really matter assuming that all implementors of the FooKey interface have a correct implementation of equals and gethashcode but I would recommend removing it since otherwise a reader of the code could get the impression that it is there because it makes a difference when in reality it does not.
You can also get rid of the '*37' in the gethashcode method, it is unlikely it would contribute to better hashcode distribution.
In terms of your question 3, I would say no, don't do that, unless the equality contract for FooKey is not controlled by you (in which case trying to enforce an equality contract for the interface is questionable anyway)
Related
I have written couple of classes which are designed to be immutable. I am trying to test them. I can certainly use MobilityDetector but I want to write something on my own. Not extensive, something basic.
The idea which I am trying to put in my test cases that on each action, the object reference for the object I performed action would be different than the object returned from the action.
For example, let's say I have designed a class say Digit and it has a method called add. So the test case I am writing is
#Test
public void test_add(){
Digit zero = Digit.getInstance(); //ignore why i am using getinstance here
Digit result = zero.add(new Random().nextInt());
assertNotEqual (zero, result); //there is no equal method overridden in Digit class
}
My assumption here is that assertNotEqual will test the reference of two objects (zero and result). If both references are different then it means that the operation performed on zero object did return a new object rather the old one.
Does this make sense?
My assumption here is that assertNotEqual will test the reference of two objects
Don't assume. Read the javadoc! It says:
Asserts that two objects are not equals. If they are, an AssertionError without a message is thrown. If unexpected and actual are null, they are considered equal.
In other words, this is using the standard Object::equals(Object) method of testing equality. That will only use == comparison if that is whaat the relevant equals(Object) method does under the hood.
To answer your question, testing for zero == result is neither a necessary or sufficient test for immutability.
zero plus some random integer could be zero
The fact that zero is == or not == to result does not prove that the zero object's state has not changed.
In fact, I don't think that there is a valid test for immutability in the general sense. The immutability property is about what is happening inside the abstraction boundary of your Digit class. If you treat Digit as a true black box, you cannot assume that you will be able to detect changes inside the box.
The only valid way to test for (true) immutability is to combine testing with (sure) knowledge of what is happening "inside the box"; i.e. code inspection of your Digit class and white-box testing.
Another alternatively, is to define what you mean by "immutability" in terms of certain externally visible attributes of Digit; e.g. what toString() returns. (But there are problems with that approach too ...)
assertEquals/assertNotEquals test for equality using the equals method of the objects. It's similar to assertTrue(expected.equals(actual)). If equal is not overridden, it will check the object's references (hashcode), but I wouldn't rely on this as this is likely to break your test if you implement the equals method eventually.
If you want to check, whether the objects are the same (or not) use assertSame/assertNoSame which test on reference equality, similar to assertTrue(expected == actual).
But to check for immutability is not only checking if any modifying operation returns a new instance, but also checking the original object is still unchanged!
One way to do this is, is to create a reference object (or a clone) of the original object and additionally check, that the original object still equals the reference object after calling add.
#Test
public void test_add(){
Digit zero = Digit.getInstance();
Digit goldenMaster = zero.clone(); //if clone is implemented
//or this
Digit goldenMaster = Digit.getInstance();
Digit result = zero.add(new Random().nextInt());
assertNotSame (zero, result); //use check for reference
assertEquals(goldenMaster, zero); //check the original object is still unmodified
}
But this in return requires you properly implement equals.
I have an object that represents an UNKNOWN value, or "Null object".
As in SQL, this object should never be equal to anything, including another UNKNOWN, so (UNKNOWN == UNKNOWN) -> false.
The object is used, however, in hashtables and the type is Comparable, so I created a class as follows:
public class Tag implements Comparable {
final static UNKNOWN = new Tag("UNKNOWN");
String id;
public Tag(String id) {
this.id = id;
}
public int hashCode(){
return id.hashCode();
}
public String toString(){
return id;
}
public boolean equals(Object o){
if (this == UNKNOWN || o == UNKNOWN || o == null || !(o instanceof Tag))
return false;
return this.id.equals(((Tag)o).id);
}
public int compareTo(Tag o){
if (this == UNKNOWN)
return -1;
if (o == UNKNOWN || o == null)
return 1;
return this.id.compareTo(o.id);
}
}
But now compareTo() seems "inconsistent"?
Is there a better way to implement compareTo()?
The documentation for compareTo mentions this situation:
It is strongly recommended, but not strictly required that
(x.compareTo(y)==0) == (x.equals(y))
Generally speaking, any class that implements the Comparable interface and violates this condition should clearly indicate this fact. The recommended language is "Note: this class has a natural ordering that is inconsistent with equals."
Therefore, if you want your object to be Comparable and yet still not allow two UNKNOWN objects to be equal via the equals method, you must make your compareTo "Inconsistent with equals."
An appropriate implementation would be:
public int compareTo(Tag t) {
return this.id.compareTo(t.id);
}
Otherwise, you could make it explicit that UNKNOWN values in particular are not Comparable:
public static boolean isUnknown(Tag t) {
return t == UNKNOWN || (t != null && "UNKNOWN".equals(t.id));
}
public int compareTo(Tag t) {
if (isUnknown(this) || isUnknown(t)) {
throw new IllegalStateException("UNKNOWN is not Comparable");
}
return this.id.compareTo(t.id);
}
You're correct that your compareTo() method is now inconsistent. It violates several of the requirements for this method. The compareTo() method must provide a total order over the values in the domain. In particular, as mentioned in the comments, a.compareTo(b) < 0 must imply that b.compareTo(a) > 0. Also, a.compareTo(a) == 0 must be true for every value.
If your compareTo() method doesn't fulfil these requirements, then various pieces of the API will break. For example, if you sort a list containing an UNKNOWN value, then you might get the dreaded "Comparison method violates its general contract!" exception.
How does this square with the SQL requirement that null values aren't equal to each other?
For SQL, the answer is that it bends its own rules somewhat. There is a section in the Wikipedia article you cited that covers the behavior of things like grouping and sorting in the presence of null. While null values aren't considered equal to each other, they are also considered "not distinct" from each other, which allows GROUP BY to group them together. (I detect some specification weasel wording here.) For sorting, SQL requires ORDER BY clauses to have additional NULLS FIRST or NULLS LAST in order for sorting with nulls to proceed.
So how does Java deal with IEEE 754 NaN which has similar properties? The result of any comparison operator applied to NaN is false. In particular, NaN == NaN is false. This would seem to make it impossible to sort floating point values, or to use them as keys in maps. It turns out that Java has its own set of special cases. If you look at the specifications for Double.compareTo() and Double.equals(), they have special cases that cover exactly these situations. Specifically,
Double.NaN == Double.NaN // false
Double.valueOf(Double.NaN).equals(Double.NaN) // true!
Also, Double.compareTo() is specified so that it considers NaN equal to itself (it is consistent with equals) and NaN is considered larger than every other double value including POSITIVE_INFINITY.
There is also a utility method Double.compare(double, double) that compares two primitive double values using these same semantics.
These special cases let Java sorting, maps, and so forth work perfectly well with Double values, even though this violates IEEE 754. (But note that primitive double values do conform to IEEE 754.)
How should this apply to your Tag class and its UNKNOWN value? I don't think you need to follow SQL's rules for null here. If you're using Tag instances in Java data structures and with Java class libraries, you'd better make it conform to the requirements of the compareTo() and equals() methods. I'd suggest making UNKNOWN equal to itself, to have compareTo() be consistent with equals, and to define some canonical sort order for UNKNOWN values. Usually this means sorting it higher than or lower than every other value. Doing this isn't terribly difficult, but it can be subtle. You need to pay attention to all the rules of compareTo().
The equals() method might look something like this. Fairly conventional:
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
return obj instanceof Tag && id.equals(((Tag)obj).id);
}
Once you have this, then you'd write compareTo() in a way that relies on equals(). (That's how you get the consistency.) Then, special-case the unknown values on the left or right-hand sides, and finally delegate to comparison of the id field:
public int compareTo(Tag o) {
if (this.equals(o)) {
return 0;
}
if (this.equals(UNKNOWN)) {
return -1;
}
if (o.equals(UNKNOWN)) {
return 1;
}
return id.compareTo(o.id);
}
I'd recommend implementing equals(), so that you can do things like filter UNKNOWN values of a stream, store it in collections, and so forth. Once you've done that, there's no reason not to make compareTo consistent with equals. I wouldn't throw any exceptions here, since that will just make standard libraries hard to use.
The simple answer is: you shouldn't.
You have contradiction requirements here. Either your tag objects have an implicit order (that is what Comparable expresses) OR you can have such "special" values that are not equal to anything, not even themselves.
As the other excellent answer and the comments point out: yes, you can somehow get there; for example by simply allowing for a.compare(b) < 0 and b.compare(a) < 0 at the same time; or by throwing an exception.
But I would simply be really careful about this. You are breaking a well established contract. And the fact that some javadoc says: "breaking the contract is OK" is not the point - breaking that contract means that all the people working on this project have to understand this detail.
Coming from there: you could go forward and simply throw an exception within compareTo() if a or b are UNKNOWN; by doing so you make at least clear that one shouldn't try to sort() a List<Tag> for example. But hey, wait - how would you find out that UNKNOWN is present in your list? Because, you know, UNKNOWN.equals(UNKNOWN) returns false; and contains() is using equals.
In essence: while technically possible, this approach causes breakages wherever you go. Meaning: the fact that SQL supports this concept doesn't mean that you should force something similar into your java code. As said: this idea is very much "off standards"; and is prone to surprise anybody looking at it. Aka "unexpected behavior" aka bugs.
A couple seconds of critical thinking:
There is already a null in Java and you can not use it as a key for a reason.
If you try and use a key that is not equal to anything else including
itself you can NEVER retrieve the value associated with that key!
I was reading Effective Java Item 9 and decided to run the example code by myself. But it works slightly different depending on how I insert a new object that I don't understand what exactly is going on inside. The PhoneNumber class looks:
public class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(int areaCode, int prefix, int lineNumber) {
this.areaCode = (short)areaCode;
this.prefix = (short) prefix;
this.lineNumber = (short)lineNumber;
}
#Override public boolean equals(Object o) {
if(o == this) return true;
if(!(o instanceof PhoneNumber)) return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode;
}
}
Then according to the book and as is when I tried,
public static void main(String[] args) {
HashMap<PhoneNumber, String> phoneBook = new HashMap<PhoneNumber, String>();
phoneBook.put(new PhoneNumber(707,867,5309), "Jenny");
System.out.println(phoneBook.get(new PhoneNumber(707,867,5309)));
}
This prints "null" and it's explained in the book because HashMap has an optimization that caches the hash code associated with each entry and doesn't check for object equality if the hash codes don't match. It makes sense to me. But when I do this:
public static void main(String[] args) {
PhoneNumber p1 = new PhoneNumber(707,867,5309);
phoneBook.put(p1, "Jenny");
System.out.println(phoneBook.get(new PhoneNumber(707,867,5309)));
}
Now it returns "Jenny". Can you explain why it didn't fail in the second case?
The experienced behaviour might depend on the Java version and vendor that was used to run the application, because since the general contract of Object.hashcode() is violated, the result is implementation dependent.
A possible explanation (taking one possible implementation of HashMap):
The HashMap class in its internal implementation puts objects (keys) in different buckets based on their hashcode. When you query an element or you check if a key is contained in the map, first the proper bucket is looked for based on the hashcode of the queried key. Inside the bucket objects are checked in a sequencial way, and inside a bucket only the equals() method is used to compare elements.
So if you do not override Object.hashcode() it will be indeterministic if 2 different objects produce default hashcodes which may or may not determine the same bucket. If by any chance they "point" to the same bucket, you will still be able to find the key if the equals() method says they are equal. If by any chance they "point" to 2 different buckets, you will not find the key even if equals() method says they are equal.
hashcode() must be overriden to be consistent with your overridden equals() method. Only in this case it is guaranteed the proper, expected and consistent working of HashMap.
Read the javadoc of Object.hashcode() for the contract that you must not violate. The main point is that if equals() returns true for another object, the hashcode() method must return the same value for both of these objects.
Can you explain why it didn't fail in the second case?
In a nutshell, it is not guaranteed to fail. The two objects in the second example could end up having the same hash code (purely by coincidence or, more likely, due to compiler optimizations or due to how the default hashCode() works in your JVM). This would lead to the behaviour you describe.
For what it's worth, I cannot reproduce this behaviour with my compiler/JVM.
In your case by coincidence JVM was able to find the same hashCode for both object. When I ran your code, in my JVM it gave null for both the case. So your problem is because of JVM not the code.
It is better to override hashCode() each and every time when you override equils() method.
I haven't read Effective Java, I read SCJP by Kathy Sierra. So if you need more details then you can read this book. It's nice.
Your last code snipped does not compile because you haven't declared phoneBook.
Both main methods should work exactly the same. There is a 1 in 16 chance that it will print Jenny because a newly crated HashMap has a default size of 16. In detail that means that only the lower 4 bits of the hashCode will be checked. If they equal the equal method is used.
I stumbled across the source of AtomicInteger and realized that
new AtomicInteger(0).equals(new AtomicInteger(0))
evaluates to false.
Why is this? Is it some "defensive" design choice related to concurrency issues? If so, what could go wrong if it was implemented differently?
(I do realize I could use get and == instead.)
This is partly because an AtomicInteger is not a general purpose replacement for an Integer.
The java.util.concurrent.atomic package summary states:
Atomic classes are not general purpose replacements for
java.lang.Integer and related classes. They do not define methods
such as hashCode and compareTo. (Because atomic variables are
expected to be mutated, they are poor choices for hash table keys.)
hashCode is not implemented, and so is the case with equals. This is in part due to a far larger rationale that is discussed in the mailing list archives, on whether AtomicInteger should extend Number or not.
One of the reasons why an AtomicXXX class is not a drop-in replacement for a primitive, and that it does not implement the Comparable interface, is because it is pointless to compare two instances of an AtomicXXX class in most scenarios. If two threads could access and mutate the value of an AtomicInteger, then the comparison result is invalid before you use the result, if a thread mutates the value of an AtomicInteger. The same rationale holds good for the equals method - the result for an equality test (that depends on the value of the AtomicInteger) is only valid before a thread mutates one of the AtomicIntegers in question.
On the face of it, it seems like a simple omission but it maybe it does make some sense to actually just use the idenity equals provided by Object.equals
For instance:
AtomicInteger a = new AtomicInteger(0)
AtomicInteger b = new AtomicInteger(0)
assert a.equals(b)
seems reasonable, but b isn't really a, it is designed to be a mutable holder for a value and therefore can't really replace a in a program.
also:
assert a.equals(b)
assert a.hashCode() == b.hashCode()
should work but what if b's value changes in between.
If this is the reason it's a shame it wasn't documented in the source for AtomicInteger.
As an aside: A nice feature might also have been to allow AtomicInteger to be equal to an Integer.
AtomicInteger a = new AtomicInteger(25);
if( a.equals(25) ){
// woot
}
trouble it would mean that in order to be reflexive in this case Integer would have to accept AtomicInteger in it's equals too.
I would argue that because the point of an AtomicInteger is that operations can be done atomically, it would be be hard to ensure that the two values are compared atomically, and because AtomicIntegers are generally counters, you'd get some odd behaviour.
So without ensuring that the equals method is synchronised you wouldn't be sure that the value of the atomic integer hasn't changed by the time equals returns. However, as the whole point of an atomic integer is not to use synchronisation, you'd end up with little benefit.
I suspect that comparing the values is a no-go since there's no way to do it atomically in a portable fashion (without locks, that is).
And if there's no atomicity then the variables could compare equal even they never contained the same value at the same time (e.g. if a changed from 0 to 1 at exactly the same time as b changed from 1 to 0).
AtomicInteger inherits from Object and not Integer, and it uses standard reference equality check.
If you google you will find this discussion of this exact case.
Imagine if equals was overriden and you put it in a HashMap and then you change the value. Bad things will happen:)
equals is not only used for equality but also to meet its contract with hashCode, i.e. in hash collections. The only safe approach for hash collections is for mutable object not to be dependant on their contents. i.e. for mutable keys a HashMap is the same as using an IdentityMap. This way the hashCode and whether two objects are equal does not change when the keys content changes.
So new StringBuilder().equals(new StringBuilder()) is also false.
To compare the contents of two AtomicInteger, you need ai.get() == ai2.get() or ai.intValue() == ai2.intValue()
Lets say that you had a mutable key where the hashCode and equals changed based on the contents.
static class BadKey {
int num;
#Override
public int hashCode() {
return num;
}
#Override
public boolean equals(Object obj) {
return obj instanceof BadKey && num == ((BadKey) obj).num;
}
#Override
public String toString() {
return "Bad Key "+num;
}
}
public static void main(String... args) {
Map<BadKey, Integer> map = new LinkedHashMap<BadKey, Integer>();
for(int i=0;i<10;i++) {
BadKey bk1 = new BadKey();
bk1.num = i;
map.put(bk1, i);
bk1.num = 0;
}
System.out.println(map);
}
prints
{Bad Key 0=0, Bad Key 0=1, Bad Key 0=2, Bad Key 0=3, Bad Key 0=4, Bad Key 0=5, Bad Key 0=6, Bad Key 0=7, Bad Key 0=8, Bad Key 0=9}
As you can see we now have 10 keys, all equal and with the same hashCode!
equals is correctly implemented: an AtomicInteger instance can only equal itself, as only that very same instance will provably store the same sequence of values over time.
Please recall that Atomic* classes act as reference types (just like java.lang.ref.*), meant to wrap an actual, "useful" value. Unlike it is the case in functional languages (see e.g. Clojure's Atom or Haskell's IORef), the distinction between references and values is rather blurry in Java (blame mutability), but it is still there.
Considering the current wrapped value of an Atomic class as the criterion for equality is quite clearly a misconception, as it would imply that new AtomicInteger(1).equals(1).
One limitation with Java is that there is no means of distinguishing a mutable-class instance which can and will be mutated, from a mutable-class instance which will never be exposed to anything that might mutate it(*). References to things of the former type should only be considered equal if they refer to the same object, while references to things of the latter type should often be considered equal if the refer to objects with equivalent state. Because Java only allows one override of the virtual equals(object) method, designers of mutable classes have to guess whether enough instances will meet the latter pattern (i.e. be held in such a way that they'll never be mutated) to justify having equals() and hashCode() behave in a fashion suitable for such usage.
In the case of something like Date, there are a lot of classes which encapsulate a reference to a Date that is never going to be modified, and which want to have their own equivalence relation incorporate the value-equivalence of the encapsulated Date. As such, it makes sense for Date to override equals and hashCode to test value equivalence. On the other hand, holding a reference to an AtomicInteger that is never going to be modified would be silly, since the whole purpose of that type centers around mutability. An AtomicInteger instance which is never going to be mutated may, for all practical purposes, simply be an Integer.
(*) Any requirement that a particular instance never mutate is only binding as long as either (1) information about its identity hash value exists somewhere, or (2) more than one reference to the object exists somewhere in the universe. If neither condition applies to the instance referred to by Foo, replacing Foo with a reference to a clone of Foo would have no observable effect. Consequently, one would be able to mutate the instance without violating a requirement that it "never mutate" by pretending to replace Foo with a clone and mutating the "clone".
I am clueless here...
1: private static class ForeignKeyConstraint implements Comparable<ForeignKeyConstraint> {
2: String tableName;
3: String fkFieldName;
4:
5: public int compareTo(ForeignKeyConstraint o) {
6: if (this.tableName.compareTo(o.tableName) == 0) {
7: return this.fkFieldName.compareTo(o.fkFieldName);
8: }
9: return this.tableName.compareTo(o.tableName);
10: }
11: }
In line 6 I get from FindBugs: Bug: net.blabla.SqlFixer$ForeignKeyConstraint defines compareTo(SqlFixer$ForeignKeyConstraint) and uses Object.equals()
Link to definition
I don't know how to correct this.
This errors means that you're not overriding equals in ForeignKeyConstraint (and thus inheriting the equals from Object) so the following is not true (from the javadoc of compareTo):
It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y)). Generally speaking, any class that implements the Comparable interface and violates this condition should clearly indicate this fact. The recommended language is "Note: this class has a natural ordering that is inconsistent with equals."
To fix the FindBugs check, override equals - and hashCode - if it makes sense which is generally the case (or exclude the check for this class and document that your class violates this condition using the suggested note).
It's telling you that there's the potential for compareTo() and equals() to disagree. And they should, really, never disagree.
The equals() method is being inherited from java.lang.Object, which by default checks to see if two objects are the same instance. Your compareTo method is comparing objects are based on tableName and fkFieldName. So you'll potentially find yourself in a situation where compareTo states that two objects are the same (because tableName and fkFieldName match), but equals states they are different (because they're different instances).
There are a few java APIs that depend on compareTo and equals being consistant; this is part of the java language and is considered a core language contract. Ideally implement an equals (and hashcode) method to check for equality based on tableName and fkFieldName.
You can solve it by implementing an equals() method. Refer to the FindBugs definition:
"Generally, the value of compareTo should return zero if and only if equals returns true. If this is violated, weird and unpredictable failures will occur in classes such as PriorityQueue."
"It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y))."
Another example is the TreeSet. It implements equality checks by invoking compareTo, and a compareTo implementation that is inconsistent with equals makes the TreeSet violate the contract of the Set interface, which might lead to program malfunction.
Have you tried overriding the equals method as well in SqlFixer.ForeignKeyConstraint?
I believe the basis of the warning is that, as stated in the definition, strange things can happen if you override compareTo and not equals.
For more information check out Joshua Bloch's Effective Java, 2nd Edition. Item 12 goes more in depth about the ins and outs of implementing Comparable and some of the things to look out for.
Findbugs is happy with:
public int compareTo(ForeignKeyConstraint o) {
if (this.equals(o)) {
return 0;
} else if (this.tableName.equals(o.tableName)) {
// fkFieldName must be different
return this.fkFieldName.compareTo(o.fkFieldName);
} else {
// tableName must be different
return this.tableName.compareTo(o.tableName);
}
}
#Override
public equals() {
...
}
#Override
public int hashCode() {
...
}