Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

This noncompliant code example defines a CaseInsensitiveString class that 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, the CaseInsensitiveString.equals() method should not attempt to interoperate with objects of the String class.

Code Block
bgColor#FFCCCC

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;
  }

  // Comply with MET09-J
  public int hashCode() {/* ... */}

  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
  }
}

...

In this compliant solution, the CaseInsensitiveString.equals() method is simplified to operate only on instances of the CaseInsensitiveString class, consequently preserving symmetry.

Code Block
bgColor#ccccff

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 int hashCode() {/* ... */}

  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 defines 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;
  }

  public int hashCode() {/* ... */}

}

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 int hashCode() {/* ... */}

  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 noncompliant code example, p1 and p2 compare equal and p2 and p3 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 the field type.

Compliant Solution (Delegation)

Unfortunately, in this case it is impossible to extend an instantiable class (as opposed to an abstract class) by the Card class by adding a value or field in the subclass while preserving the equals() contract. Use composition rather than inheritance to achieve the desired effect [Bloch 2008]. This compliant solution adopts this approach by adding a private card field to the XCard class 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 int hashCode() {/* ... */}

  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
  }
}
Prints false
    System.out.println(p1.equals(p3)); // Prints false
    System.out.println(p1.equals(p4)); // Prints true
  }
}

Compliant Solution (Class Comparison)

If the Card.equals() method could unilaterally assume that two objects with distinct classes were not equal, it could be used in an inheritance hierarchy while preserving transitivity. .

Code Block
bgColor#ccccff
public class Card {
  private final int number;

  public Card(int number) {
    this.number = number;
  }

  public boolean equals(Object o) {
    if (!(o.getClass() == this.getClass())) {
      return false;
    }

    Card c = (Card)o;
    return c.number == number;
  }

  public int hashCode() {/* ... */}

}

Noncompliant Code Example (Consistency)

...

Consider an application that allows an organization's employees to access an external mail service via http://mailwebsite.com. The application is designed to deny access to other websites by behaving as a makeshift firewall. However, a crafty or malicious user could nevertheless access an illegitimate website http://illegitimatewebsite.com if it were hosted on the same computer as the legitimate website and consequently shared the same IP address. Even worse, if the legitimate website were hosted on a server in a commercial pool of servers, an attacker could register multiple websites in the pool (for phishing purposes) until one was registered on the same computer as the legitimate website, consequently defeating the firewall.

Code Block
bgColor#FFCCCC

public class Filter {
  public static void main(String[] args) throws MalformedURLException {
    final URL allowed = new URL("http://mailwebsite.com");
    if (!allowed.equals(new URL(args[0]))) {
      throw new SecurityException("Access Denied");
    }
    // Else proceed
  }
}

...

This compliant solution compares two URLs' string representations, thereby avoiding the pitfalls of URL.equals().

Code Block
bgColor#ccccff

public class Filter {
  public static void main(String[] args) throws MalformedURLException {
    final URL allowed = new URL("http://mailwebsite.com");
    if (!allowed.toString().equals(new URL(args[0]).toString())) {
      throw new SecurityException("Access Denied");
    }
    // Else proceed
  }
}

...

This compliant solution uses a URI object instead of a URL. The filter appropriately blocks the website when presented with any string other than http://mailwebsite.com because the comparison fails.

Code Block
bgColor#ccccff

public class Filter {
  public static void main(String[] args)
                     throws MalformedURLException, URISyntaxException {
    final URI allowed = new URI("http://mailwebsite.com");
    if (!allowed.equals(new URI(args[0]))) {
      throw new SecurityException("Access Denied");
    }
    // Else proceed
  }
}

...

This noncompliant code example compares two keys using the equals() method. The comparison may return false even when the key instances represent the same logical key.

Code Block
bgColor#FFCCCC

private static boolean keysEqual(Key key1, Key key2) {
  if (key1.equals(key2)) {
    return true;
  }
  return false;
}

...

This compliant solution uses the equals() method as a first test and then compares the encoded version of the keys to facilitate provider-independent behavior. For example, this code can determine whether a RSAPrivateKey and RSAPrivateCrtKey represent equivalent private keys [Sun 2006].

Code Block
bgColor#ccccff

private static boolean keysEqual(Key key1, Key key2) {
  if (key1.equals(key2)) {
    return true;
  }

  if (Arrays.equals(key1.getEncoded(), key2.getEncoded())) {
    return true;
  }

  // More code for different types of keys here.
  // For example, the following code can check if
  // an RSAPrivateKey and an RSAPrivateCrtKey are equal:
  if ((key1 instanceof RSAPrivateKey) &&
      (key2 instanceof RSAPrivateKey)) {
  
    if ((((RSAKey)key1).getModulus().equals(
         ((RSAKey)key2).getModulus())) &&
       (((RSAPrivateKey) key1).getPrivateExponent().equals(
        ((RSAPrivateKey) key2).getPrivateExponent()))) {
      return true;
    }
  }
  return false;
}

...

[API 2006]

Method equals()

[Bloch 2008]

Item 8. Obey the general contract when overriding equals

[Darwin 2004]

9.2, Overriding the equals Method

[Harold 1997]

Chapter 3, Classes, Strings, and Arrays, The Object Class (Equality)

[Sun 2006]

Determining If Two Keys Are Equal (JCA Reference Guide)

[Techtalk 2007]

More Joy of Sets

 

      05. Methods (MET)      MET09-J. Classes that define an equals() method must also define a hashCode() method