...
This noncompliant code example violates transitivity though it satisfies the symmetry requirementdefines an XCard
class that extends the Card
class.
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 noncompliant code example, p1
and p2
returns true
, in the second, the comparison between compare equal and p2
and p3
also returns true
but in the third, the comparison between p1
and p3
returns false
. This contradicts the transitivity rule compare equal but p1
and p3
compare unequal; violating the transitivity requirement. The problem is that the Card
class has no knowledge of the XCard
class and consequently cannot determine that p2
and p3
have different values for type
. Unfortunately, it is impossible to extend an instantiable class (as opposed to an abstract
class) by adding a value or field in the subclass while preserving the equals()
contract.
Compliant Solution
Wiki Markup |
---|
ItBecause it is currently notimpossible 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 preferredused instead overof inheritance. This technique does qualify as a reasonable workaround \[[Bloch 2008|AA. Bibliography#Bloch 08]\]. This compliant Itsolution canadopts bethis implementedapproach by adding givinga theprivate {{XCardcard}} classfield ato privatethe {{cardXCard}} fieldclass and providing a {{public}} {{viewCard()}} method. |
Code Block |
---|
|
class XCard {
private String type;
private Card card; // Composition
public XCard(int number, String type) {
card = new Card(number);
this.type = type;
}
public Card viewCard() {
return card;
}
public boolean equals(Object o) {
if (!(o instanceof XCard)) {
return false;
}
XCard cp = (XCard)o;
return cp.card.equals(card) && cp.type.equals(type);
}
public static void main(String[] args) {
XCard p1 = new XCard(1, "type1");
Card p2 = new Card(1);
XCard p3 = new XCard(1, "type2");
XCard p4 = new XCard(1, "type1");
System.out.println(p1.equals(p2)); // Prints false
System.out.println(p2.equals(p3)); // Prints false
System.out.println(p1.equals(p3)); // Prints false
System.out.println(p1.equals(p4)); // Prints true
}
}
|
Wiki Markup |
---|
"There are some classes in the Java platform libraries that do extend an instantiable class and add a value component. For example, {{java.sql.Timestamp}} extends {{java.util.Date}} and adds a nanoseconds field. The {{equals}} implementation for {{Timestamp}} does violateviolates symmetry and can cause erratic behavior if {{Timestamp}} and {{Date}} objects are used in the same collection or are otherwise intermixed." \[[Bloch 2008|AA. Bibliography#Bloch 08]\] |
...