Item 10: Observe common convention when override equals

  Override the equals method may seem simple, but there are many ways to rewrite causes an error, and the consequences are very serious. The easiest way to avoid this problem is not covered equals method, in this case, each instance of a class can only be equal to its own. If any of the following conditions are met, and that is the correct approach:

  • Each instance of the class is unique.  For class activities on behalf of an entity rather than the value (value) is indeed the case, such as Thread. Equals Object provided with full implementation of correct behavior (The equals implementation provided by Object has exactly the right behavior for these classes) for these classes.

  • I do not care whether the class provides a "logical equivalent (logical equality)" test function.  For example, java.util.regex.Pattern can override equals two checks Pattern instance is represented in exactly the same regular expression, but designers do not think that clients need or want this feature. In this case, from a succession of equals Object is the ideal way.

  • Superclass has overridden the equals, inherited from the superclass behavior for over a subclass also suitable.  For example, most Set implementations inherit equals realized from AbstractSet, List inheritance equals realized from AbstractList, Map implementation inheritance equals realized from AbstractMap.

  • Class is private or package-level private, and can determine its equals method will never be called.  If you are very risk-averse, you can override the equals method to ensure that it will not be accidentally call:

@Override public boolean equals(Object o) {
    throw new AssertionError(); // Method is never called
}

  So when rewriting the equals method is appropriate? When a class has a logic concept equal (same concept different from the object itself), but also does not override the superclass equals. This is typically the case, "the value of the class (value class)" a. It refers to a class value of a value indicating the class only, or for example, Integer String. Program ape when referring to compare objects using the equals method, I want to know if they are equal in logic, rather than know whether they refer to the same object. In order to meet the demand program ape, not only must override the equals method, but doing so makes the instance of this class can be used as a map (Map) key (Key), or a collection (set) the elements of the map or a set of exhibit the expected behavior.

  There is a "value-based" do not need to rewrite the equals method of ensuring singleton pattern class [] "as much as there is only one value for each object" controlled by the examples (item 1). Enumerated type (item 34) belongs to this category. For such a class, the logic is the same as the object equivalent thing, and therefore equals method of Object equals method is equivalent to the logical sense.

  When you override the equals method, you must abide by its generic conventions. These are the conventions of content, specification from the Object:

  • Reflexive (Reflexive) : X, of x.equals (X) must return true for any non-null reference value.
  • Symmetry (the Symmetric) : For any non-null reference values x and y, if and only if y.equals returns true (x), x.equals (y ) must return true.
  • Transitivity (Transitive,) : For any non-null reference values x, y and z, if x.equals (y) returns true, and y.equals (z) returns true, then x.equals (z) must also return true.
  • Consistency (the Consistent) : For any non-null reference values x and y, as long as the information in the object equals the comparison operation has not been used in the modified multiple calls x.equals (y) returns true uniform or consistent return return false.
  • For any non-null reference value x, x.equals (null) must return false.

  Unless you are particularly interested in math, otherwise these provisions may seem a little terrified, but never ignore these rules! If you violate them, you will find your program behave properly, or even collapse, and it is difficult to stop the root cause of failure (and it can be very difficult to pin down the source of the failure). Quoted the words of John Donne, no class is isolated. Instance of a class is generally transmitted frequently to the instances of another class. There are many categories, including all of the collections (collections classes) included, are passed to depend on whether they comply with the object equals convention.

  Now you know how terrible violation of equals agreement, now we come to a more detailed discussion of these conventions. The good news is, these conventions may seem very scary, in fact, is not complicated. Once you understand these conventions, they are not difficult to follow.

  So what is the equivalent to do with it? Roughly speaking, it is an operator, it will be a set of elements into subsets, subsets of these elements is considered to be equal to each other. These subsets called equivalence class. To be useful equals method, all of the elements in each equivalence class must be interchangeable from the user's perspective. Now we turn to check five requirements:

  Reflexive (reflexivity) ---- The first requirement is merely illustrative of the object must be equal to itself. It is difficult to imagine a violation unconsciously. If contrary to this one, and then add the instance of the class to the collection (collection) in, contains a set of methods that will tell you, this collection does not contain examples you just added.

  Symmetry (Symmetry) ---- The second requirement is that any two objects to "whether they are equal," the question must be consistent. The first requirement with different, if inadvertently violate Article, this situation has not been difficult to imagine. For example, consider the following class, which implements a case insensitive string. String is saved by the toString, but was ignored in comparison operations:

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

  In this class, it intended equals method is very good, it is attempted to interoperate with a normal string (String) objects. Suppose we have a string and a normal string case-insensitive:

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";

As expected, cis.equals(s)it returns true. The problem is that, although the equals method CaseInsensitiveString class known normal string (String) object, but equals method String class but does not know the insensitive string. Therefore, s.equals(cis)it returns false, in clear violation of symmetry. Suppose you have a case-insensitive string object into a collection:

List<CaseInsensitiveString> list = new ArrayList<>();
list.add(cis);

At this point list.contains (s) will return the result? Who knows? In the current implementation of OpenJDK, which happens to be returned it is false, but this is only the result of this particular implementation only. In other sight, it is possible to return true, or a runtime exception is thrown. Once you break the agreement equals, when other objects in the face of your subject, you do not know the behavior of these objects happen.

  To solve this problem, just an attempt to put this code and interoperable removed from the String equals on it. By doing so, we can reconstruct this method, it becomes a separate return statement:

@Override public boolean equals(Object o) {
    return o instanceof CaseInsensitiveString && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}

  Transitive ---- the equals agreed third requirement is that, if the first object is equal to the second object and the second object is equal to the third object, the first object is a constant equal to the third object. Similarly, the case of unconscious violation of this rule is not difficult to imagine. Consider the case of sub-categories, it will a new value components (value component) is added to the superclass. In other words, the subclass added information will affect the result of the comparison of equals. We begin with a simple two-dimensional integer immutable Point class as a start:

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
}

Suppose you want to inherit this class, adding color information to a point:

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
}

  equals methods happen? If we do not provide the equals method, but directly inherited from Point came in equals comparison, when the color information was ignored. Although this does not violate the agreement equals, but is clearly unacceptable. Suppose you write an equals method only if its argument is another colored dot, and have the same position and color, it will return true:

// Broken - violates symmetry!
@Override public boolean equals(Object o) {
    if (!(o instanceof ColorPoint))
        return false;
    return super.equals(o) && ((ColorPoint) o).color == color;
}

  The problem with this approach is that, when a point is compared with a color point, may give different results, and vice versa. The former comparison ignores the color information, comparing the latter always returns false, because the type parameter is incorrect. To visually illustrate the problem, we create a point and a colored dot:

Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);

Then, p.equals(cp)it returns true, cp.equals (p) false is returned. This you can do to try to fix this problem, let ColorPoint.equals carrying color information is ignored when "mixed comparison":

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

This approach does provide symmetry, but at the expense of delivery:

ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);

At this time, p1.equals(p2)and pe.equals(p3)all returns true, but p1.equals(p3)it returns false, it is a clear violation of transitivity. The first two are less consider color information ( "color blindness"), and the third comparison is considered the color information.

  Again, this method can lead to infinite recursion: Suppose there are two subclasses Point, called ColorPoint and SmellPoint, each subclass uses this equals method, then calling myColorPoint. equals (mySmellPonit) will throw a stack overflow exception (StackOverflowError).

  So how to solve it? This is an object-oriented language, a basic question about the equivalence relation. Our colleagues can not be instantiated in the extended class, both to increase the value of new components, while retaining equals agreement, unless willing to give up the advantages of object-oriented abstraction brings.

  You may have heard, with the test in place of the equals method getClass instanceof tests, may be extended and increase in instances of the class value component, while retaining the agreement equals:

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

  This procedure only when the object has the same implementation, in order to make the object equivalent. While this is not too bad, but the result is not acceptable: an instance of Point continues to be a subclass of Point, it still needs to run as a function, but if this method is that it can not do that! Let's suppose we want to write a method to determine whether a point on the unit circle. This is one way we can do:

// Initialize unitCircle to contain all Points on the unit circle
private static final Set<Point> unitCircle = Set.of(
    new Point( 1, 0), new Point( 0, 1),
    new Point(-1, 0), new Point( 0, -1));

public static boolean onUnitCircle(Point p) {
    return unitCircle.contains(p);
}

  While this may not be the fastest way to achieve this functionality, but it's a very good effect. But suppose you expand the Point through some components do not add value the way, for example, it records the constructor creates a number of examples:

public class CounterPoint extends Point {
    private static final AtomicInteger counter = new AtomicInteger();
    public CounterPoint(int x, int y) {
        super(x, y);
        counter.incrementAndGet();
    }
    public static int numberCreated() { return counter.get(); }
}

  Richter substitution principle (Liskov substitution principle) that any important attribute of a type will also apply to its sub-types, so any way to write for the type, sub-type on it should also run well [Liskov87 ]. This is our earlier official statement that Ponit (such as CounterPoint) subclass Point is still, and must act as a Point to work. But suppose we will pass to onUnitCircle CounterPoint method. If the Point class based getClass the equals method, regardless CounterPoint example how x and y coordinates, onUnitCircle method will return false. This is so because, like onUnitCircle method used is a collection of such HashSet, using the equals method of verifying a condition [that is inserted object in Set collection when it will first check whether the object already exists], no CounterPonit instance with any Point examples of correspondence. However, if you use the appropriate method based on the equals instanceof Point, when faced with CounterPoint, the same onUnitCircle methods work just fine.

  Although there is no satisfactory way to extend that is not instantiated class, value added components, but still there is a good stopgap measure (workaround): On the recommendation of 18: Combination precedence over inheritance. We will not let ColorPoint Point extension, but added ColorPoint Point in a private field, as well as a public view (view) method (item 6), this method returns a position at the same point in common with the colored Point Object:

// Adds a value component without violating the equals contract
public class ColorPoint {
    private final Point point;
    private final Color color;
    public ColorPoint(int x, int y, Color color) {
        point = new Point(x, y);
        this.color = Objects.requireNonNull(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);
    }
    ... // Remainder omitted
}

  In the Java platform libraries, some class extends the class can be instantiated and added new value components. For example, java.sql.Timestamp of java.util.Date been extended and increased nanoseconds field. Timestamp of equals realize it is a violation of symmetry, if Timestamp and Date objects are used in the same set, or being mixed together in other ways, it will cause incorrect behavior. Timestamp class has a disclaimer, warning program ape Do not mix Date and Timestamp object. As long as you do not mix them together, there will be trouble, There is no other measure can prevent you from doing so, and the results will be difficult to debug errors. This behavior Timestamp class was a mistake, is not worthy of emulation.

  Note that you can in an abstract subclass (abstrace) class value add new components without violating the equals contract. This is important for the class hierarchy, you can "use the class hierarchy (class hierarchies) instead of the tag class (tagged classes)" by following the recommendations in paragraph 23 to obtain the class hierarchy. For example, you might have an abstract class Shap, it has no value components, Circle subclass adds a radius field, Rectangle subclass adds length and width fields. As long as the superclass can not directly create an instance of the previously described problems of this would have happened.

  Consistency (Consistency) ---- agreed the equals fourth requirement is that if two objects are equal, they must always remain the same, unless they have a (or both) is modified. In other words, the variable can be equal to target different objects at different times, rather than the variable objects are not. When you write a class, you should carefully consider whether it should be immutable (Item 17). If you think it should be immutable, it is necessary to ensure that the equals method to satisfy such restrictions: the object is always equal to equal, unequal objects never equal.

  No matter whether a class is immutable, do not make unreliable the equals method relies on resources. If you violate this prohibition, it is difficult to meet the conformance requirements. For example, equals java.net.URL method relies on comparison of the URL host IP address. A host name into an IP address may be required to access the network, as time goes on, does not ensure that will produce the same results. This will lead to the equals method equals the agreement violates the URL, it may cause some problems in practice. This behavior is the URL equals method is a big mistake, and should not be emulated. Unfortunately, because of compatibility requirements, this behavior can not be changed. To avoid this problem, equals deterministic calculation method should only reside in memory implementation of the object.

  Nonemptiness (Non-nullity) ---- last name is not a requirement, I will call it "non-emptiness (Non-nullity)". Means that all objects must not equal to null. Although it is difficult to imagine accidentally returns true when calling o.equals (null), but it is not difficult to imagine accidentally throw NullPointerException. General agreement is prohibited to do so. Many classes equals method are shown by a null test to prevent this:

@Override public boolean equals(Object o) {
    if (o == null)
        return false;
    ...
}

  This test is unnecessary. To test equality of its parameters, equals the method it must be converted to the appropriate type parameter, so that it can be called access method (the accessor), or access its fields. Before carrying out the conversion, equals instanceof operator must use the method, which checks whether the correct parameter type:

@Override public boolean equals(Object o) {
    if (!(o instanceof MyType))
        return false;
    MyType mt = (MyType) o;
    ...
}

  If you omit this step type checking, and the parameters passed to the equals method is the wrong type, then the equals method throws ClassCastExceptionan exception, which equals a violation of the agreement. However, if the instanceof the first operand (the operand) is null, then, regardless of the second operation target (the operand) which type, instanceof operator returns false [JLS, 15.20.2], and therefore, if the null is passed to the equals method, type checking will return false, so no separate checking null.

  Combine all of these requirements, the following tips come to realize high quality equals method:

  1. Use == operator to check "is a reference parameter for the object." If so, it returns true. This is simply a performance optimization, if the comparison may be very expensive to operate, it is worth it.
  2. Use instanceof operator to check "parameter is the correct type." If not, false is returned, in general, the so-called "right type" refers to the class equals method is located. In some cases, it refers to an interface implemented by the class. If the class implements the interface conventions equals improved, allowing implementation of the interface for the comparison between classes, then use the interface. Collection interfaces (collection interfaces) such as Set, List, Map and Map.Entry having such characteristics.
  3. The conversion parameter to the correct type. Because instanceof test conducted before the conversion, so make sure to be successful.
  4. For each such "key (Significant)" field, to check whether the field parameters corresponding to the subject field matches. If these tests are successful, it returns true, otherwise false. If the type of step 2 is an excuse must access parameter field with the interface method; If the type is a class, might be able to directly access parameter field, it is necessary depending on their accessibility.

  For neither float type nor type double base type field, may be used ==operators compared; for object reference type field, call the equals method recursively; for float fields, use the static method Float.compare(float, float)comparing method; for double field use Double.compare(double,double). Float and double fields for special treatment is necessary, because there Float.NaN, -0.0f and similar double constant; For more information, please refer to JLS 15.21.1 or Float.equals documents. Although you can use the static method Float.equals and Double.equals to compare the float and double fields, but it will automatically generate packing in comparison each time, which can lead to poor performance. For an array of fields, keep these guidelines apply to each element. If each element in the array field is very important, use a method in which Arrays.equals methods were compared.

  Some object reference types of fields contain null may be legitimate, all in order to avoid NullPointerException may lead to abnormal, use the static method Objects.equals(Object, Object)to check whether those fields are equal.

  For some categories, such as CaseInsensitiveString class, more than a simple field test for equality is much more complex. If this is the case, you may want to save the field of a "paradigm (canonical form)", this equals method can accurately relatively low cost of these paradigms, rather than non-precision high overhead. This method is most suitable for immutable classes (Item 17); if the object can change, it must ensure that its paradigm date.

  Compare the order of fields may affect the performance of the equals method. For best performance, you should first compare the most field may be inconsistent, or the field is the lowest cost, the ideal situation is to field two conditions are met. You should not compare those fields do not belong to the object logic state, for example, operation of a Lock field sync. You do not need to compare those derived fields (derived fields) can be calculated from a key field, but this field is derived [compare] it is possible to improve the performance of the equals method. If the derived field represents a comprehensive description of the entire object, compare this field can save money when compared to the failure to compare actual data needed [that is, when (a condition) derived object can represent whether two objects are equal when, at the same time (second condition) Compare this field is derived under a small overhead overhead than the case of comparing the actual data, we can use derived fields whether objects are equal comparison]. For example, assume there is a Polygon class, and the buffer region. If two instances of Polygon Polygon [] has a different area, there is no need to compare their edges and vertices.

  1. When you finish writing the equals method, you should ask yourself three questions: whether it has symmetry? Whether transitive? It is consistent? And do not just ourselves, but also to write unit tests to test these features, unless you use AutoValue [Google is an open source framework, mentioned below] (original page 49) is automatically generated equals method, in which case, you can safely ignore test. If the property can not be maintained, to find out why, and modify the equals method. Of course, your equals method must meet two additional properties (reflexivity and non-emptiness), but the two usually are interlinked (If the properties fail to hold, figure out why, and modify the equals method accordingly. of course your equals method must also satisfy the other two properties (reflexivity and non-nullity), but these two usually take care of themselves).

  It shows the equals method according to the previous configuration of the tips in this simple class PhoneNumber:

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

  The following are some final instructions:

  • Always override the hashCode method (item 11) When overridden the equals method.
  • Do not try to be too smart to let the equals method. If you simply test the value of a domain are equal, it is not hard to do to comply with the agreement equals. If you want to seek excessive equivalence relation, it is easy to fall into trouble. Any form of any one of the aliases contemplated to be within the range of equivalents, often a bad idea. For example, File class should not try to point to the same file symbolic link (symbolic link) as equal objects to look at. Fortunately, the File class did not do so.
  • Do not Object object equals declaration substituting other types. The following program ape to write this equals method is not uncommon, this program will spend hours apes are not quite sure why it does not work:
// Broken - parameter type must be Object!
public boolean equals(MyClass o) {
    ...
}

  The problem is that this method does not override the Object.equals method because its parameters should be of type Object, on the contrary, it overloads the Object.equals (Item 52). On the basis of the original equals methods, and then provide a "strongly typed (strongly typed)" equals method, which is unacceptable, because it can lead to sub-class Override annotation produce false positives and providing false sense of security (because it can cause Override annotations in subclasses to generate false positives and provide a false sense of security) [may lead to Override annotation subclass at compile time error].

  Override annotation consistent usage, as shown in this section, such mistakes can be prevented (item 40). This equals method does not compile, an error message will tell you in the end what went wrong:

// Still broken, but won’t compile
@Override public boolean equals(MyClass o) {
    ...
}

  Writing and testing equals (and hashCode) method is very complicated, the resulting code is very common. A good alternative to manually write and test these methods is to use Google's open source AutoValue framework that will automatically generate these methods for you, triggered by a single class of notes. In most cases, AutoValue generating method as that you have written yourself basically the same.

  IDE tools also generate equals and hashCode method, but the result is more verbose than source code uses AutoValue more easily read, do not automatically change in the tracing class, and therefore need to be tested. In other words, let the IDE generate equals (and hashCode) method is usually more desirable than manually implement them, because the IDE does not cause careless mistakes, humans do the same.

  In short, do not override the equals method, unless you have to do: In many cases, inherited from Object achieve full compliance with your request. If you do rewrite equals, be sure to compare all the key fields of the class, and use the five tips mentioned before to test it (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).

Guess you like

Origin www.cnblogs.com/coloured_glaze/p/11702733.html