I'm studying Effective Java, Item 8 (Obey the general contract when overriding equals). It has been explained quite clearly by the author, but still some parts are not that much elaborated.
For this example, he considers a class CaseInsensitiveString defined as :
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}
// Broken - violates symmetry!
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
if (o instanceof String) // One-way interoperability!
return s.equalsIgnoreCase((String) o);
return false;
}
// ... // Remainder omitted
}
In the end of the article, he says :
For some classes, such as CaseInsensitiveString above, field comparisons are more complex than simple equality tests. If this is the case, you may want to store a canonical form of the field, so the equals method can do cheap exact comparisons on these canonical forms rather than more costly inexact compar- isons. This technique is most appropriate for immutable classes (Item 15); if the object can change, you must keep the canonical form up to date.
I searched for this term and found that it basically means a standard representation of something, like absolute path without any symbolic links for a file in a directory. But I'm unable to understand the use of 'canonical' form for this class, which would help here. Any suggestions?
I think in this particular example, a canonical form might be storing the lowercase or uppercase version of the String and doing comparisons on that.
private final String s;
public CaseInsensitiveString(String s) {
//for real code probably use locale version
this.s = s.toLowerCase();
}
This makes the equality comparison cheaper because we can do exact string comparisons instead of the more expensive equalsIgnoreCase
// Broken - violates symmetry!
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equals(((CaseInsensitiveString) o).s);
if (o instanceof String) // One-way interoperability!
return s.equals((String) o);
return false;
}