ALTHOUGH
The easiest way to avoid problems is not to override the
Each instance of the class is inherently unique
There is no need for the class to provide a “logical equality” test.
A superclass has already overridden
@Override public boolean equals(Object o) {
throw new AssertionError(); // Method is never called
}
The
Object
is a concrete class, it is designed primarily for extension. All of its nonfinal methods (equals
, hashCode
, toString
, clone
, and finalize
) have explicit general contracts because they are designed to be overridden.The easiest way to avoid problems is not to override the
equals
method, in which case each instance of the class is equal only to itself.Each instance of the class is inherently unique
There is no need for the class to provide a “logical equality” test.
A superclass has already overridden
equals
, and the superclass behavior is appropriate for this class. @Override public boolean equals(Object o) {
throw new AssertionError(); // Method is never called
}
The
equals
method implements an equivalence relation. It has these properties
• Reflexive: For any non-null reference value
x
, x.equals(x)
must return true
.
• Symmetric: For any non-null reference values
x
and y
, x.equals(y)
must return true
if and only if y.equals(x)
returns true
.
• Transitive: For any non-null reference values
x
, y
, z
, if x.equals(y)
returns true
and y.equals(z)
returns true
, then x.equals(z)
must return true
.
• Consistent: For any non-null reference values
x
and y
, multiple invocations of x.equals(y)
must consistently return true
or consistently return false
, provided no information used in equals
comparisons is modified.
• For any non-null reference value
x
, x.equals(null)
must return false
.
Reflexivity—The first requirement says merely that an object must be equal to itself. It’s hard to imagine violating this one unintentionally. If you were to violate it and then add an instance of your class to a collection, the
contains
method might well say that the collection didn’t contain the instance that you just added.
Symmetry—The second requirement says that any two objects must agree on whether they are equal. Unlike the first requirement, it’s not hard to imagine violating this one unintentionally. For example, consider the following class, which implements a case-insensitive string. The case of the string is preserved by
toString
but ignored in equals
comparisons:
// Broken - violates symmetry!
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
this.s = Objects.requireNonNull(s);
}
// Broken - violates symmetry!
@Override public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(
((CaseInsensitiveString) o).s);
if (o instanceof String) // One-way interoperability!
return s.equalsIgnoreCase((String) o);
return false;
}
... // Remainder omitted
}
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
this.s = Objects.requireNonNull(s);
}
// Broken - violates symmetry!
@Override public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(
((CaseInsensitiveString) o).s);
if (o instanceof String) // One-way interoperability!
return s.equalsIgnoreCase((String) o);
return false;
}
... // Remainder omitted
}
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
String s = "polish";
cis.equals(s)
returns true
. The problem is that while the equals
method in CaseInsensitiveString
knows about ordinary strings.
the
equals
method in String
is oblivious to case-insensitive strings. Therefore, s.equals(cis)
returns false
, a clear violation of symmetry
List<CaseInsensitiveString> list = new ArrayList<>();
list.add(cis);
list.add(cis);
list.contains(s)
Once you’ve violated the
equals
contract, you simply don’t know how other objects will behave when confronted with your object.
you can refactor the method into a single return statement:
@Override public boolean equals(Object o) {
return o instanceof CaseInsensitiveString &&
((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
return o instanceof CaseInsensitiveString &&
((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
Transitivity—The third requirement of the
equals
contract says that if one object is equal to a second and the second object is equal to a third, then the first object must be equal to the third. Again, it’s not hard to imagine violating this requirement unintentionally. Consider the case of a subclass that adds a new value component to its superclass. In other words, the subclass adds a piece of information that affects equals
comparisons. Let’s start with a simple immutable two-dimensional integer point class:
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return p.x == x && p.y == y;
}
... // Remainder omitted
}
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return p.x == x && p.y == y;
}
... // Remainder omitted
}
public class ColorPoint extends Point {
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
... // Remainder omitted
}
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
... // Remainder omitted
}
// Broken - violates symmetry!
@Override public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
@Override public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
// Broken - violates transitivity!
@Override public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
// If o is a normal Point, do a color-blind comparison
if (!(o instanceof ColorPoint))
return o.equals(this);
// o is a ColorPoint; do a full comparison
return super.equals(o) && ((ColorPoint) o).color == color;
}
@Override public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
// If o is a normal Point, do a color-blind comparison
if (!(o instanceof ColorPoint))
return o.equals(this);
// o is a ColorPoint; do a full comparison
return super.equals(o) && ((ColorPoint) o).color == color;
}
This approach does provide symmetry, but at the expense of transitivity:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
Now
p1.equals(p2)
and p2.equals(p3)
return true
, while p1.equals(p3)
returns false
, a clear violation of transitivity. The first two comparisons are “color-blind,” while the third takes color into account.
// Broken - violates Liskov substitution principle (page 43)
@Override public boolean equals(Object o) {
if (o == null || o.getClass() != getClass())
return false;
Point p = (Point) o;
return p.x == x && p.y == y;
}
@Override public boolean equals(Object o) {
if (o == null || o.getClass() != getClass())
return false;
Point p = (Point) o;
return p.x == x && p.y == y;
}
@Override public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint) o;
return cp.point.equals(point) && cp.color.equals(color);
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint) o;
return cp.point.equals(point) && cp.color.equals(color);
}
For example,
java.sql.Timestamp
extends java.util.Date
and adds a nanoseconds
field.The equals
implementation for Timestamp
does violate symmetry and can cause erratic behavior if Timestamp
and Date
objects are used in the same collection or are otherwise intermixed.
Consistency—The fourth requirement of the
equals
contract says that if two objects are equal, they must remain equal for all time unless one (or both) of them is modified. In other words, mutable objects can be equal to different objects at different times while immutable objects can’t. When you write a class, think hard about whether it should be immutable (Item 17). If you conclude that it should, make sure that your equals
method enforces the restriction that equal objects remain equal and unequal objects remain unequal for all time.
Whether or not a class is immutable, do not write an
equals
method that depends on unreliable resources.
@Override public boolean equals(Object o) {
if (!(o instanceof MyType))
return false;
MyType mt = (MyType) o;
...
}
if (!(o instanceof MyType))
return false;
MyType mt = (MyType) o;
...
}
If this type check were missing and the
equals
method were passed an argument of the wrong type, the equals
method would throw a ClassCastException
,But the instanceof
operator is specified to return false
if its first operand is null
, regardless of what type appears in the second operand [JLS, 15.20.2]. Therefore, the type check will return false
if null
is passed in, so you don’t need an explicit null
check.
Putting it all together, here’s a recipe for a high-quality
equals
method:
1. Use the
==
operator to check if the argument is a reference to this object. If so, return true
. This is just a performance optimization but one that is worth doing if the comparison is potentially expensive.
2. Use the
instanceof
operator to check if the argument has the correct type. If not, return false
. Typically, the correct type is the class in which the method occurs. Occasionally, it is some interface implemented by this class. Use an interface if the class implements an interface that refines the equals
contract to permit comparisons across classes that implement the interface. Collection interfaces such as Set
, List
, Map
, and Map.Entry
have this property.
3. Cast the argument to the correct type. Because this cast was preceded by an
instanceof
test, it is guaranteed to succeed.
4. For each “significant” field in the class, check if that field of the argument matches the corresponding field of this object. If all these tests succeed, return
true
; otherwise, return false
. If the type in Step 2 is an interface, you must access the argument’s fields via interface methods; if the type is a class, you may be able to access the fields directly, depending on their accessibility.
For primitive fields whose type is not
float
or double
, use the ==
operator for comparisons; for object reference fields, call the equals
method recursively; for float
fields, use the static Float.compare(float, float)
method; and for double
fields, use Double.compare(double, double)
.
While you could compare
float
and double
fields with the static methods Float.equals
and Double.equals
, this would entail autoboxing on every comparison, which would have poor performance.
For best performance, you should first compare fields that are more likely to differ, less expensive to compare, or, ideally, both.You need not compare derived fields, which can be calculated from “significant fields,” but doing so may improve the performance of the
equals
method.For example, suppose you have a Polygon
class, and you cache the area. If two polygons have unequal areas, you needn’t bother comparing their edges and vertices.
When you are finished writing your
equals
method, ask yourself three questions: Is it symmetric? Is it transitive? Is it consistent?
// Class with a typical equals method
public final class PhoneNumber {
private final short areaCode, prefix, lineNum;
public PhoneNumber(int areaCode, int prefix, int lineNum) {
this.areaCode = rangeCheck(areaCode, 999, "area code");
this.prefix = rangeCheck(prefix, 999, "prefix");
this.lineNum = rangeCheck(lineNum, 9999, "line num");
}
private static short rangeCheck(int val, int max, String arg) {
if (val < 0 || val > max)
throw new IllegalArgumentException(arg + ": " + val);
return (short) val;
}
@Override public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNum == lineNum && pn.prefix == prefix
&& pn.areaCode == areaCode;
}
... // Remainder omitted
}
public final class PhoneNumber {
private final short areaCode, prefix, lineNum;
public PhoneNumber(int areaCode, int prefix, int lineNum) {
this.areaCode = rangeCheck(areaCode, 999, "area code");
this.prefix = rangeCheck(prefix, 999, "prefix");
this.lineNum = rangeCheck(lineNum, 9999, "line num");
}
private static short rangeCheck(int val, int max, String arg) {
if (val < 0 || val > max)
throw new IllegalArgumentException(arg + ": " + val);
return (short) val;
}
@Override public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNum == lineNum && pn.prefix == prefix
&& pn.areaCode == areaCode;
}
... // Remainder omitted
}
• Don’t try to be too clever. If you simply test fields for equality, it’s not hard to adhere to the
equals
contract. If you are overly aggressive in searching for equivalence, it’s easy to get into trouble. It is generally a bad idea to take any form of aliasing into account. For example, the File
class shouldn’t attempt to equate symbolic links referring to the same file. Thankfully, it doesn’t.
• Don’t substitute another type for
Object
in the equals
declaration. It is not uncommon for a programmer to write an equals
method that looks like this and then spend hours puzzling over why it doesn’t work properly:
In summary, don’t override the
equals
method unless you have to: in many cases, the implementation inherited from Object
does exactly what you want. If you do override equals
, make sure to compare all of the class’s significant fields and to compare them in a manner that preserves all five provisions of the equals
contract.
No comments:
Post a Comment