Effective Java Third edition--10. Adhere to common conventions when overriding the Equals method

Source: Internet
Author: User
Tags manual writing

Tips
"Effective Java, third Edition" an English version has been published, the second edition of this book presumably many people have read, known as one of the four major Java books, but the second edition of 2009 published, to now nearly 8 years, but with Java 6, 7, 8, and even 9 of the release, the Java language has undergone profound changes.
In the first time here translated into Chinese version. For everyone to learn to share.

10. Compliance with common conventions when overriding the Equals method

Although Object it is a specific class, it is primarily designed for inheritance. All of its non-final methods (equals, Hashcode, toString, clone, and finalize) have clear universal conventions (general contracts) because they are designed to be overridden by a quilt class. Any class has an obligation to override these methods to conform to their common conventions, and if not, it will prevent other conventions-dependent classes (such as HashMap and HashSet) from working properly with this class.

This chapter discusses when and how to override Object a non-final method of a class. This chapter omits the Finalize method because it is discussed in article 8. Comparable.compareTomethods, although not Object in the method, because there are a lot of similarities, so also discussed here.

Overriding the Equals method looks simple, but there are many ways to make the rewrite error, and the result can be scary. The simplest way to avoid this problem is not to overwrite the Equals method, in which case each instance of the class is equal to itself. This is the correct approach if any of the following conditions are true:

    • An instance of each class is inherently unique. This is true for classes that represent activity entities, such as thread, rather than values. The equals implementation provided by object is exactly the right behavior for these classes.
    • Class does not need to provide a test function for "logical equality (logical equality)". For example java.util.regex.Pattern , you can override the Equals method to check whether two represent the exact same pattern instance of a regular expression, but the designer does not think the customer needs or wants to use this feature. In this case, the equals implementation inherited from object is the most appropriate.
    • The parent class has overridden the Equals method, and the parent class behavior is entirely appropriate for that subclass. For example, most of the set inherits the equals implementation from Abstractset, the list inherits the equals implementation from Abstractlist, and map inherits the equals implementation from map Abstractmap.
    • The class is private or package-level private and can be determined that its Equals method is never called. If you are very risk averse, you can override the Equals method to ensure that it is not accidentally called:

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

When do you need to rewrite the Equals method? If a class contains a concept of logical equality (logical equality), this concept differs from object identity, and the parent class has not overridden the Equals method. This is typically used in the case of a value class (value classes). A value class is just a class that represents a value, such as an integer or a string class. Programmers use the Equals method to compare references to value objects, expecting to see if they are logically equal, rather than referencing the same object. Overriding the Equals method not only satisfies the programmer's expectations, it also supports overriding the instance of equals as the key of the map, or the elements in the Set to meet the expected and expected behavior.

A value class that does not need to be overridden by the Equals method is a class that uses instance control (instance control) (entry 1) to ensure that at most one object exists for each value. The enumeration type (entry 34) belongs to this category. For these classes, logical equality is the same as the object identity, so the Equals method of object acts logically as the Equals method.

When you rewrite the Equals method, you must abide by its general conventions. The specification for object is as follows:
The Equals method implements an equivalence relationship (equivalence relation). It has the following properties:
? reflexivity: For any non-null reference x, x.equals(x) you must return true.
Symmetry: For any non-null reference x and Y, you must return TRUE if and only if y.equals(x) true is returned x.equals(y) .
? transitivity: For any non-null reference x, Y, Z, if True returns True, you x.equals(y) y.equals(z) x.equals(z) must return True.
? Consistency: For any non-null reference x and Y, x.equals(y) multiple calls must always return TRUE or always return FALSE if the information used in the Equals comparison is not modified.
? For any non-null reference x, x.equals(null) you must return FALSE.

Unless you like math, it looks a little scary, but don't ignore it! If it is violated, it is likely to find that your program is running abnormally or crashes, and it is difficult to determine the root cause of the failure. As John Donne John donne, no class exists in isolation. An instance of a class is often passed to an instance of another class. Many classes, including all collection classes, rely on objects that are passed to them in compliance with the equals Convention.

Now that we are aware of the danger of violating the covenant of equals, let us discuss this contract in detail. The good news is, on the face of it, it's not very complicated. Once you understand it, it is not difficult to abide by this agreement.

So what is an equivalence relationship? Generally speaking, it is an operator that divides a set of elements into a subset of equal elements of each other. These subsets are called equivalence classes (equivalence classes). For the Equals method to be useful, all elements in each equivalence class must be interchangeable (interchangeable) from the user's point of view. Now let's look at these five requirements in turn:

Reflexivity (reflexivity)--the first requirement is that an object must be equal to itself. It is hard to imagine inadvertently violating this rule. If you violate it and then add an instance of the class to a collection, the contains method might say that the collection does not contain the instance you just added.

Symmetry (symmetry)-the second requirement is that any two objects must agree on the issue of equality. Unlike the first requirement, it is not difficult to imagine that this requirement has been inadvertently violated. For example, consider the following class, which implements a case-insensitive string. The string is persisted by ToString, but is ignored in the equals comparison:

import java.util.Objects;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}

equals in the above class attempts to manipulate the normal string, assuming we have a case-insensitive string and a normal string:

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");String s = "polish”;System.out.println(cis.equals(s)); // trueSystem.out.println(s.equals(cis)); // false

As expected, cis.equals(s) returns TRUE. The problem is that although the CaseInsensitiveString equals method in the class knows the normal string, the Equals method in the string class ignores the case-insensitive string. Therefore, s.equals(cis returns false, which is a clear violation of symmetry. Suppose you put a case-insensitive string into a collection:

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

list.contains(s)What was returned? Who knows? In the current OPENJDK implementation, it returns false, but this is only an implementation artifact. In another implementation, it is easy to return true or throw a run-time exception. Once you violate the equals convention, you don't know how other objects will behave when confronted with your object.

To eliminate this problem, simply remove the malicious attempt to manipulate the string class in the Equals method. When you do this, you can refactor the method into a single return statement:

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

The third requirement of the transitive (transitivity)--equals convention is that if the first object equals the second object and the second object equals the third object, then the first object must be equal to the third object. Similarly, it is not difficult to imagine, inadvertently violated this requirement. Consider the case of a subclass and add the new value component (value component) to its parent class. In other words, the subclass adds a message that affects the Equals method comparison. Let's start with a simple immutable two-dimensional integer type 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}

Suppose you want to inherit from this class, add a color class that represents colors to the point class:

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}

What should the Equals method look like? If completely ignored, the implementation is inherited from the point class, and the color information is ignored in the Equals method comparison. While this does not violate the covenant of equals, it is clearly unacceptable. Suppose you write a equals method that returns true only if its argument is another ColorPoint instance that has the same position and color:

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

When you compare a point object with a ColorPoint object, you can get different results, and vice versa. The comparison of the former ignores the color attribute, and the latter comparison will always return false, because the type of the parameter is wrong. To make the problem more specific, we create a point object and a ColorPoint object:

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

P.equals (CP) returns True, but Cp.equals (p) returns false. You may want to use colorpoint.equals to solve this problem by mixing comparisons.

@Overridepublic 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 it loses its transitivity:

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

Now, p1.equals(p2) and p2.equals(p3) returns True, but p1.equals(p3) returns false, clearly violating the requirements of transitivity. The first two comparisons do not take into account color information, while the third one contains color information.

In addition, this approach can lead to infinite recursion: Assume that there are two sub-classes of point, such as ColorPoint and Smellpoint, each of which has this equals method. The call myColorPoint.equals(mySmellPoint) will then throw a Stackoverflowerror exception.

So what's the solution? It turns out that this is a basic problem of equivalence relation in object-oriented language. Unless you are willing to abandon the benefits of object-oriented abstraction, you cannot inherit a class that can be instantiated and add a value component while preserving the equals convention.

As you may have heard, you can inherit a class that can be instantiated and add a value component, while preserving the equals convention by using a GetClass test in the Equals method instead of the instanceof test:

@Overridepublic boolean equals(Object o) {    if (o == null || o.getClass() != getClass())        return false;    Point p = (Point) o;    return p.x == x && p.y == y;}

The same effect occurs only if the object has the same implementation class. This may not seem so bad, but the result is unacceptable: an instance of a point class subclass is still an instance of point, and it still needs to be run as a point, but if you take this approach, it will fail! Suppose we are going to write a method to determine whether a point object is in the Unitcircle collection. We can do this:

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 quickest way to implement functionality, it works fine. Suppose you inherit a point class in a simple way that does not add a value component, such as how many instances have been created by its construction method tracking record:

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();    }}

The Richter substitution principle (Liskov substitution principle) states that any important attribute of any type should apply to all subtypes, so any method written for this type should also be applied on its subclasses [Liskov87]. This is a formal statement that we have previously stated that a subclass of point, such as counterpoint, is still a point and must be treated as a point class. However, suppose we pass a Counterpoint object to the Onunitcircle method. If the point class uses the Equals method based on GetClass, the Onunitcircle method returns false regardless of the x and Y coordinates of the counterpoint instance. This is because most collections, including the HashSet used by the Onunitcircle method, use the Equals method to test for the inclusion of an element, and the counterpoint instance does not equal any point instance. However, if you use the appropriate Equals method on a point instanceof , the same Onunitcircle method works correctly when you use Counterpoint instance rendering.

Although there is no satisfactory way to inherit a class that can be instantiated and add a value component, there is a good workaround: "Use combination instead of inheritance" as suggested in entry 18. Instead of inheriting the ColorPoint class that inherits the point class, you can define a private and a common attempt (view) (entry 6) method in the ColorPoint class to return a ColorPoint object with the same location.

 //Adds A value component without violating the Equals Contractpublic 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}  

There are classes in the Java Platform Class library that inherit classes that can be instantiated and add a value component. For example, java.sql.Timestamp java.util.Date a nanoseconds field has been inherited and added. The equivalent equals of timestamp does violate symmetry and can cause erratic behavior if the timestamp and date objects are used in the same collection or otherwise mixed. The timestamp class has a disclaimer that warns programmers not to mix timestamp and date. While it's not a hassle to use them separately, nothing can prevent you from mixing them together, and the resulting errors can be difficult to debug. This behavior of the timestamp class is a mistake and should not be emulated.

You can add a value component to a subclass of an abstract class without violating the equals convention. This is important for the class level that is obtained by following the recommendation in the 23rd entry, "Prioritizing class hierarchies" in place of the recommendations in the tag class (tagged classes). For example, you can have an abstract class that does not have a value component shape, a subclass circle has a RADIUS attribute, and another subclass rectangle contains the length and width properties. As long as you do not create the parent class instance directly, the problem shown earlier does not occur.

The fourth requirement of the conformance--equals convention is that if two objects are equal, unless one (or two) objects are modified, they must always remain equal. In other words, mutable objects can be equal to different objects at different times, while immutable objects do not. When you write a class, think carefully whether it should be designed as immutable (entry 17). If you think you should do this, make sure your equals method enforces such restrictions: equal objects are always equal, and unequal objects are never equal.

Regardless of whether a class is immutable, do not write a equals method that relies on unreliable resources. If this prohibition is violated, it is very difficult to meet the consistency requirements. For example, the Equals method in the Java.net.URL class relies on a comparison of the IP address of the host associated with the URL. Converting a host name to an IP address may require access to the network, and there is no guarantee that the same result will occur over time. This may cause the Equals method of the URL class to violate the Equals Convention and cause problems in practice. The behavior of the Equals method of the URL class is a big mistake and should not be emulated. Unfortunately, because of the compatibility requirements, it cannot be changed. To avoid this problem, the Equals method should only perform deterministic computations on memory-resident objects.

Non-nullability (non-nullity)--The last covenant requirement of equals has no official name, so I venture to call it "non-void". It means that all objects must not be equal to NULL. Although it is hard to imagine that the response in the call would o.equals(null) return true in the field, it is not difficult to imagine accidentally throwing an NullPointerException exception. Generic conventions prohibit the throwing of such exceptions. The Equals method in many classes will explicitly block the case of NULL for the object:

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

This judgment is not necessary. In order to test whether its parameters are equal, the Equals method must first convert its arguments to the appropriate type in order to invoke accessors or allow access to the property. Before performing a type conversion, the method must use the instanceof operator to check whether its arguments are of the correct type:

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

If this type of check is omitted and the Equals method passes a parameter of the wrong type, then the Equals method throws an ClassCastException exception, which violates the equals convention. However, if the first operand is null, the specified instanceof operator returns false, regardless of the type [jls,15.20.2] that occurs in the second operand. Therefore, if NULL is passed in, the type check returns FALSE, so no explicit null check is required.

Together, here is the recipe for writing the high-quality equals method (recipe):

    1. Use the = = operator to check if the parameter is a reference to the object. If yes, returns TRUE. This is just a performance optimization, but if this comparison is likely to be expensive, it's worth doing.
    2. Use instanceof the operator to check if the parameter has the correct type. If it is not, it returns false. In general, the correct type is the same class as the Equals method. Sometimes, the class implements some interfaces. If a class implements an interface that can improve the equals convention to allow the class that implements the interface to be compared, then the interface is used. Collection interfaces, such as Set,list,map and map.entry, have this attribute.
    3. The parameter is converted to the correct type. Because the conversion operation has been processed in instanceof, it will certainly succeed.
    4. For each "important" property in the class, check that the parameter property matches the property that corresponds to the object. If all of these tests succeed, return true, otherwise false is returned. If the type in step 2 is an interface, the properties of the parameter must be accessed through an interface method, and if the type is a class, the property can be accessed directly, depending on the access rights of the property.

For basic types that are non-float or double, use the = = operator for comparisons, call the Equals method recursively for object reference properties, use static methods for properties of the float base type, and Float.compare(float, float) for properties of a double primitive type, using Double.compare(double, double) the Method. Because of the existence Float.NaN , -0.0f and the value of a similar double type, special handling of the float and double properties is required; For more information, see the detailed documentation for the JLS 15.21.1 or Float.equals method. Although you can use the static methods Float.equals and Double.equals methods to compare properties of float and double primitives, this results in automatic boxing on each comparison, resulting in very poor performance. For array properties, apply these guidelines to each element. If each element in an array property is important, use one of the overloaded arrays.equals methods.

Some objects reference properties that may legitimately contain null. To avoid nullpointerexception exceptions, use the static method Objects.equals (object, object) to check that these properties are equal.

For some classes, such as CaseInsensitiveString classes on, property comparisons are much more complex than simple equality tests. In this case, you want to save a canonical form of the property (canonical form), so that the Equals method can do a very small, precise comparison based on this canonical form, instead of a costly non-standard comparison. This approach is best suited for immutable classes (entry 17). Once the object has changed, be sure to update the corresponding canonical form to the latest.

The performance of the Equals method may be affected by the order of attribute comparisons. For best performance, you should first compare the most likely different attributes, the less expensive attributes, or preferably both (derived fields). You do not compare attributes that are not part of the object's logical state, such as the lock property for synchronization operations. You do not need to compare derived properties that can be computed from the important attribute, but doing so can improve the performance of the Equals method. If the derived property is equivalent to a summary description of the entire object, comparing this property will save the cost of comparing the actual data when the comparison fails. For example, suppose you have a polygon class and cache the zone. If the area of the two polygons is not equal, you do not have to bother to compare their edges and vertices.

When you finish writing the Equals method, ask yourself three questions: Is it symmetrical? Is it transitive? In other words, write unit tests to troubleshoot, unless you use the Autovalue Framework (page 49th) to generate the Equals method, in which case you can safely omit the test. If the owning property fails, identify the cause and modify the Equals method accordingly. Of course, the Equals method must also satisfy the other two properties (reflexive and non-nullability), but these two properties are usually met.

The following is a simple PhoneNumber class that shows the Equals method built on the previous recipe:

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}

Here are some final reminders:

    1. When overriding the Equals method, you also override the Hashcode method (entry 11).
    2. do not let the Equals method try to be too smart . If you simply test for attributes that are used for equality, it is not difficult to comply with the equals convention. If you're too aggressive in finding equality, it's easy to get into trouble. In general, considering any form of alias is usually a bad idea. For example, the file class should not attempt to equate a referenced symbolic link with the same file object. Fortunately, the File class did not do so.
    3. in the equal method declaration, do not replace the parameter object with another type . It's not uncommon for programmers to write a equals method that looks like this, and then spend hours pondering why it doesn't work:

       //Broken-parameter type must be object! public boolean equals (MyClass o) {...}  
      The problem with the

      is that this method does not override the Object.Equals method, whose arguments are of type object, which simply overloads the Equals method (Item 52). Even in addition to the normal method, it is unacceptable to provide this "strongly typed" Equals method, because it can cause the override annotations in subclasses to produce false positives and provide an insecure illusion.
      here, using the override annotations will prevent you from making this error (entry 40). This equals method does not compile, and the error message tells you exactly where it was wrong:

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

      Writing and testing the Equals (and Hashcode) methods is cumbersome and the raw code is common. The elegant way to replace manual writing and testing of these methods is to use the Google Autovalue Open source framework, which automatically generates these methods for you, simply by adding an annotation to the class. In most cases, the Autovalue framework generates a method that is essentially the same as the method you write yourself.

Many Ides, such as Eclipse,netbeans,intellij idea, also have the ability to generate the Equals and Hashcode methods, but the generated source code is more verbose and less readable than using the Autovalue framework, and does not automatically track changes in the class , so tests are required. This means that using IDE tools to generate equals (and Hashcode) methods is generally preferable to writing them manually, because IDE tools do not make careless mistakes, and humans do.

In summary, unless you have to: in many cases, do not override the Equals method, and the implementation from object inheritance is exactly what you want. If you do override the Equals method, be sure to compare all the important attributes of the class and compare them in the same way as the five rules in the previous equals convention.

Effective Java Third edition--10. Adhere to common conventions when overriding the Equals method

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.