...
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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] | |
Item 8. Obey the general contract when overriding equals | |
9.2, Overriding the | |
Chapter 3, Classes, Strings, and Arrays, The Object Class (Equality) | |
[Sun 2006] | Determining If Two Keys Are Equal (JCA Reference Guide) |
More Joy of Sets |
05. Methods (MET) MET09-J. Classes that define an equals() method must also define a hashCode() method