Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: fixed up separate example

...

This noncompliant code example violates transitivity though it satisfies the symmetry requirementdefines an XCard class that extends the Card class.

Code Block
bgColor#FFCCCC
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
bgColor#ccccff
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]\]

...