...
This noncompliant code example defines a CaseInsensitiveString class that defines includes a String and overrides the equals() method. The CaseInsensitiveString class knows about ordinary strings but the String class has no knowledge of case-insensitive strings. Consequently, CaseInsensitiveString.equals() method should not attempt to interoperate with objects of the String class.
| Code Block | ||
|---|---|---|
| ||
public final class CaseInsensitiveString {
private String s;
public CaseInsensitiveString(String s) {
if (s == null) {
throw new NullPointerException();
}
this.s = s;
}
// This method violates symmetry
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString) {
return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
}
if (o instanceof String) {
return s.equalsIgnoreCase((String)o);
}
return false;
}
public static void main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("Java");
String s = "java";
System.out.println(cis.equals(s)); // Returns true
System.out.println(s.equals(cis)); // Returns false
}
}
|
As a resultBy operating on String objects, the CaseInsensitiveString.equals() method violates the second contract requirement (symmetry). Because of the asymmetry, a String object s and CaseInsensitiveString object cis that only differ in case, cis.equals(s)) returns true while s.equals(cis) returns false. This violates the second contract requirement (symmetry).
Compliant Solution
The new In this compliant solution, the CaseInsensitiveString.equals() method is highlighted in this compliant solutionsimplified to only operation on instances of the CaseInsensitiveString class.
| Code Block | ||
|---|---|---|
| ||
public final class CaseInsensitiveString {
private String s;
public CaseInsensitiveString(String s) {
if (s == null) {
throw new NullPointerException();
}
this.s = s;
}
public boolean equals(Object o) {
return o instanceof CaseInsensitiveString &&
((CaseInsensitiveString)o).s.equalsIgnoreCase(s);
}
public static void main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("Java");
String s = "java";
System.out.println(cis.equals(s)); // Returns false now
System.out.println(s.equals(cis)); // Returns false now
}
}
|
...
This noncompliant code example violates transitivity though it satisfies the symmetry requirement. In the first print statement, the comparison between p1 and p2 returns true, in the second, the comparison between p2 and p3 also returns true but in the third, the comparison between p1 and p3 returns false. This contradicts the transitivity rule.
| Code Block | ||
|---|---|---|
| ||
public class Card {
private final int number;
public Card(int number) {
this.number = number;
}
public boolean equals(Object o) {
if (!(o instanceof Card)) {
return false;
}
Card c = (Card)o;
return c.number == number;
}
}
class XCard extends Card {
private String type;
public XCard(int number, String type) {
super(number);
this.type = type;
}
public boolean equals(Object o) {
if (!(o instanceof Card)) {
return false;
}
// Normal Card, do not compare type
if (!(o instanceof XCard)) {
return o.equals(this);
}
// It is an XCard, compare type as well
XCard xc = (XCard)o;
return super.equals(o) && xc.type == type;
}
public static void main(String[] args) {
XCard p1 = new XCard(1, "type1");
Card p2 = new Card(1);
XCard p3 = new XCard(1, "type2");
System.out.println(p1.equals(p2)); // Returns true
System.out.println(p2.equals(p3)); // Returns true
System.out.println(p1.equals(p3)); // Returns false, violating transitivity
}
}
|
In the first print statement, the comparison between p1 and p2 returns true, in the second, the comparison between p2 and p3 also returns true but in the third, the comparison between p1 and p3 returns false. This contradicts the transitivity rule.
Compliant Solution
| Wiki Markup |
|---|
It is currently not possible to extend an instantiable class (as opposed to an {{abstract}} class) and add a value or field in the subclass while preserving the {{equals()}} contract. This implies that composition must be preferred over inheritance. This technique does qualify as a reasonable workaround \[[Bloch 2008|AA. Bibliography#Bloch 08]\]. It can be implemented by giving the {{XCard}} class a private {{card}} field and providing a {{public}} {{viewCard()}} method. |
...