It is often difficult to meet the need to overwrite equals.
When do I need to overwrite equals?
- Each instance of a class is inherently unique, and we do not need to use special logical values to express that the Equals method provided by object is exactly right.
- The superclass has overridden equals, and the behavior inherited from the superclass is also appropriate for subclasses.
- When the Equals method of the class is determined not to be called, such as the class is private.
If you want to ask when you need to overwrite equals?
The answer is just the opposite of the previous question.
That is, the class requires a logical equality concept of its own, and the equals provided by the superclass does not satisfy its own behavior.
(PS: logical equality and object equality are one thing for enumerations.) )
Now that we have to cover equals, we need to abide by some rules:
- Reflexive (reflexive): True for any one non-null reference value X,x.equals (x).
- Symmetry (symmetric): Y.equals (x) is true for any non-null reference value x and Y,x.equals (y) is true.
- transitivity (transitive): for any non-null reference value x, Y, and Z, when X.equals (y) is true and Y.equals (z) is true, X.equals (z) is true.
- Consistency (consistent): for any non-null reference value x and Y, the result of multiple calls to X.equals (y) remains the same as long as the information used by the comparison operation for equals is not modified in the object.
(PS: X,x.equals (NULL) must return FALSE for any non-null reference value. )
In fact, it is well understood that these rules are taken casually.
The difficulty is that when I comply with a rule, it is possible to violate another rule .
Reflexivity is needless to say, it's hard to think of someone violating this.
For symmetry, here's a negative example:
class CaseInsensitiveString { private final String s; public CaseInsensitiveString(String s) { if (s == null) this.s = StringUtils.EMPTY; else this.s = s; } @Override public boolean equals(Object obj) { if (obj instanceof CaseInsensitiveString) return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s); if (obj instanceof String) return s.equalsIgnoreCase((String) obj); return false; }}
This example is clearly a violation of symmetry, that is, X.equals (y) is true but Y.equals (x) is false.
Unpredictable behavior occurs not only when a call is displayed, but when you place the type as a generic into a collection.
For the above example, in the Equals method I will not be involved in other types, the decision to remove the string instance can be.
For transitivity, that is, when X.equals (y) is true and Y.equals (z) is true, X.equals (z) is true.
This rule is especially noticeable when extending a class.
For example, I use X, y to describe a point:
class Point { private final int x; private final int y; public Point(int x, int y) { super(); this.x = x; this.y = y; } @Override public boolean equals(Object obj) { if (!(obj instanceof Point)) return false; Point p = (Point) obj; return p.x == x && p.y == y; }}
Now I want to add color to point:
class ColorPoint extends Point { private final Color color; public ColorPoint(int x, int y, Color color) { super(x, y); this.color = color; } @Override public boolean equals(Object obj) { if (!(obj instanceof ColorPoint)) return false; return super.equals(obj) && ((ColorPoint) obj).color == color; }}
It seems natural to offer the colorpoint equals method, but he does not even have the symmetry to satisfy.
So we modify it to satisfy the symmetry:
@Overridepublic boolean equals(Object obj) { if (!(obj instanceof Point)) return false; if (!(obj instanceof ColorPoint)) return obj.equals(this); return super.equals(obj) && ((ColorPoint) obj).color == color;}
Well, then we should consider transitivity.
For example, we now have three instances, 1 point and 2 colorpoint ....
Then obviously, not satisfied < when X.equals (Y) is true and Y.equals (z) is true then X.equals (z) is true>.
In fact, we cannot add new value components while preserving the equals convention while extending the class that can be instantiated.
So I simply do not instanceof, instead of GetClass ().
This can really solve the problem, but it is very difficult to accept.
If I have a subclass that does not overwrite equals, the result of equals at this time is always false.
That being the case, I would abandon inheritance and use compound (composition) instead.
Using the above ColorPoint as an example, turn point into a colorpoint field instead of expanding. The code is as follows:
public class ColorPoint { private final Point point; private final Color color; public ColorPoint(int x, int y, Color color) { if (color == null) throw new NullPointerException(); point = new Point(x, y); this.color = color; } /** * Returns the point-view of this color point. */ public Point asPoint() { return point; } @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); } @Override public int hashCode() { return point.hashCode() * 33 + color.hashCode(); }}
With respect to consistency, that is, if the two are equal, they are always equal unless a party is modified.
Rather than the Equals method, this class should be designed to be variable or immutable when thinking about writing a class.
If it is immutable, consistency needs to be ensured.
With these provisions in mind, here are some suggestions for overriding equals:
- The first step uses the "= =" operation to verify that the reference is the same, so that unnecessary comparison operations are done.
- Use instanceof to check the type of the parameter.
- Check all critical field, and use the "= =" comparison directly with the base type field other than float and double.
- Go back and check again: Are you satisfied with reflexivity, symmetry, transitivity, and consistency.
Effective Java-note overwrite equals