Valid Java, inclutivejava
It is difficult to overwrite equals.
When do I not need to overwrite equals?
- Every instance of the class is unique in nature. We do not need to express it with special logical values. The equals method provided by the Object is correct.
- The superclass already covers equals, and the behavior inherited from the superclass is also suitable for the subclass.
- When the equals method of this class is determined not to be called, for example, the class is private.
When do I need to overwrite equals?
The answer is exactly the opposite of the previous question.
That is, the class requires a unique logical equality concept, and the equals provided by the superclass does not satisfy its own behavior.
(PS: for enumeration, equal logic and equal object are the same thing .)
Since equals has to be overwritten, we need to follow some rules:
- Reflexive: For any non-null reference value x, x. equals (x) is true.
- Symmetric Ric: For any non-null reference values x and y, if x. equals (y) is true, y. equals (x) is true.
- Transitive: For any non-null reference values x, y, and z, when x. equals (y) is true and y. if equals (z) is true, x. equals (z) is true.
- Consistent: For any non-null reference values x and y, as long as the information used in the comparison operation of equals in the object is not modified, multiple calls of x. the equals (y) results are consistent.
(PS: For any non-null reference value x, x. equals (null) must return false .)
In fact, these rules are easy to understand.
The difficulty lies in,When I comply with one rule, it may violate another rule..
It's hard to think that someone will violate this point.
The following is a negative example of symmetry:
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 clearly violates symmetry, that is, x. equals (y) is true, but y. equals (x) is false.
Not only is this type displayed when it is put as a wildcard in a set or another place, it will be unpredictable.
For the above example, I will not involve other types in the equals method, so I can remove the String Instance's judgment.
About the transmission, that is, when x. equals (y) is true and y. equals (z) is true, x. equals (z) is true.
This rule is especially evident in class extension.
For example, I use x and 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 some color to the 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 that the ColorPoint equals method is provided, but its symmetry is not satisfied.
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;}
Now, we should consider the transmission.
For example, we now have three instances, one Point and two ColorPoint ....
Then it is clear that the parameter does not meet <when x. equals (y) is true and y. equals (z) is true, x. equals (z) is true>.
In fact, we cannot extend the instantiated class while adding new value components and retaining the equals conventions.
So I simply don't need instanceof, instead use getClass ().
This can indeed solve the problem, but it is very unacceptable.
If a subclass does not overwrite equals, The equals result is always false.
In this case, I gave up inheritance and switched to composition ).
The preceding ColorPoint is used as an example to convert a Point into a ColorPoint field instead of being extended. 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(); }}
For consistency, that is, if the two are equal, they are always equal unless one party is modified.
This is not so much about the equals method as it is about writing a class. This class should be designed to be variable or immutable.
If it is immutable, consistency must be ensured.
With these requirements in mind, the following are some suggestions for rewriting equals:
- The first step is to use the "=" operation to verify whether it is the same reference, so as to avoid unnecessary comparison operations.
- Use instanceof to check the parameter type.
- Check all key fields and use "=" to compare the fields of basic types other than float and double.
- Go back and check again: whether the self-inversion, symmetry, transmission and consistency are satisfied.