Week 8: hashCode() method & equals() method

In the Java language, the use of equals() and hashCode() is closely coordinated. If you design one of them yourself, you must design the other. In most cases, these two functions are not considered, and their default design can be used directly. However, in some cases, these two functions are best designed by themselves to ensure the normal operation of the entire program. The most common is when an object is added to the collection object, these two functions must be designed by themselves. A more refined definition is: if you want to put an object A into another collection object B, or use this object A as the key to find the location of a meta object in collection object B, and support whether to accommodate, delete the collection object For operations such as the meta object in B, the equals() and hashCode() functions must be defined by the developer. In other cases, these two functions do not need to be defined.

1. equal() method

It is used to compare two objects, the content of the object, and of course, the comparison of the reference value of the object. What is object reference value comparison? It is the worth comparison of two reference variables. We all know that the value of the reference variable is actually a number, and this number can be regarded as a code to identify different objects. The comparison of the reference value of two objects is the comparison of two numbers and the comparison of two codes. This comparison is the default object comparison method. In the Object object, this method has been designed. So you don't have to rewrite it yourself, wasting unnecessary time.

The equals() method in the Object class is implemented as follows:

public boolean equals(Object obj) {
    return (this == obj);
}

 It can be seen from this implementation that the implementation of the Object class adopts the algorithm with the highest degree of discrimination, that is, as long as the two objects are not the same object, equals() must return false.

Although we can override the equals() method when defining a class, there are some caveats; the JDK specifies the conventions that should be followed when implementing the equals() method:

(1) Reflexivity: x.equals(x) must return true.

(2) Symmetry: The return values ​​of x.equals(y) and y.equals(x) must be equal.

(3) Transitivity: x.equals(y) is true, y.equals(z) is also true, then x.equals(z) must be true.

(4) Consistency: If neither the information used by objects x nor y in equals() has changed, then the value of x.equals(y) remains unchanged.

(5) Non-null: x is not null and y is null, then x.equals(y) must be false.

2. hashCode() method

1、Object的hashCode()

The declaration of the hashCode() method in the Object class is as follows:

public native int hashCode();

可以看出,hashCode()是一个native方法,而且返回值类型是整形;实际上,该native方法将对象在内存中的地址作为哈希码返回,可以保证不同对象的返回值不同。

Similar to the equals() method, the hashCode() method can be overridden. The role of the hashCode() method in JDK and the precautions for implementation are explained:

(1) hashCode() works in hash tables, such as java.util.HashMap.

(2) If the information used by the object in equals() has not changed, then the hashCode() value remains unchanged.

(3) If two objects are judged to be equal using the equals() method, the hashCode() method should also be equal.

(4) If two objects are judged to be unequal using the equals() method, hashCode() is not required to be unequal; however, developers should realize that unequal objects produce different hashCodes, which can improve the performance of hash tables. performance.

2. The role of hashCode()

In general, hashCode() works in hash tables, such as HashSet, HashMap, etc.

When we add an object object to a hash table (such as HashSet, HashMap, etc.), first call the hashCode() method to calculate the hash code of the object, and the position of the object in the hash table can be directly located through the hash code (usually Hash code modulo the size of the hash table). If there is no object in the position, you can directly insert the object into the position; if there are objects in the position (there may be more than one, implemented through a linked list), call the equals() method to compare whether these objects are equal to the object. If they are equal, no need Save the object; if not equal, add the object to the linked list.

This also explains why equals() are equal, then hashCode() must be equal. If two objects equals() are equal, they should only appear once in the hash table (such as HashSet, HashMap, etc.); if hashCode() is not equal, then they will be hashed to different locations in the hash table, ha It appears more than once in the table.

In fact, in the JVM, the loaded object includes three parts in memory: object header, instance data, and padding. Among them, the object header includes a pointer to the type of the object and MarkWord. In addition to the GC generation age information and lock status information of the object, the MarkWord also includes the hashcode of the object; the object instance data is the valid information that the object actually stores. ; The padding part only acts as a placeholder, because HotSpot requires that the starting address of the object must be an integer multiple of 8 bytes.

3. How to rewrite hashCode()

This section first introduces the principles that should be followed when rewriting the hashCode() method, and then introduces the general hashCode() rewriting method.

1. The principle of rewriting hashcode()

From the previous description, we know that rewriting hashCode needs to comply with the following principles:

(1) If the equals() method is overridden, check whether the condition "two objects are judged to be equal using the equals() method, the hashCode() method should also be equal" is true, and if not, rewrite the hashCode() method.

(2) The hashCode() method cannot be too simple, otherwise there will be too many hash collisions.

(3) The hashCode() method cannot be too complicated, otherwise the computational complexity will be too high and the performance will be affected.

2. hashCode() override method

A simple and general hashCode algorithm is proposed in Effective Java

A. Initialize an integer variable and assign a non-zero constant value to this variable, such as int result = 17;
B. Select all fields used for comparison in the equals method (the reason why only the fields used in equals() are selected is that To guarantee No. 1 of the above principle), then compute for each domain's properties:

(1) If it is a boolean value, calculate f ? 1:0
(2) If it is byte\char\short\int, calculate (int)f
(3) If it is a long value, calculate (int)(f ^ (f >>> 32))
(4) If it is a float value, calculate Float.floatToIntBits(f)
(5) If it is a double value, calculate Double.doubleToLongBits(f), and then the returned result is long, and then use Rule (3) to deal with long, get int
(6) If it is an object application, if the comparison method of recursive call is adopted in the equals method, then the method of recursive call of hashCode is also adopted in hashCode. Otherwise, a normal form needs to be calculated for this field. For example, when the value of this field is null, the hashCode value is 0.
(7) If it is an array, then each element needs to be treated as a separate field. The java.util.Arrays.hashCode method includes the hashCode calculation of 8 basic types of arrays and reference arrays, and the algorithm is the same as above.

C. Finally, the hash code of each field is merged into the hash code of the object. 

An example is given below. In this example, the Person class overrides the equals() method and the hashCode() method. Because only the name field and the age field are used in the equals() method, only the name field and the age field are calculated in the hashCode() method.

For the name field of type String, the hashCode() method of String is directly used; for the age field of type int, its value is directly used as the hash of the field.

public class Person {
    private String name;
    private int age;
    private boolean gender;
  
    public Person() {
        super();
    }
  
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public boolean isGender() {
        return gender;
    }
    public void setGender(boolean gender) {
        this.gender = gender;
    }
  
    @Override
    public boolean equals(Object another) {
        if (this == another) {
            return true;
        }
        if (another instanceof Person) {
            Person anotherPerson = (Person) another;
            if (this.getName().equals(anotherPerson.getName()) && this.getAge() == anotherPerson.getAge()) {
                return true;
            } else {
                return false;
            }
        }
        return false;
    }
  
    @Override
    public int hashCode() {
        int hash = 17;
        hash = hash * 31 + getName().hashCode();
        hash = hash * 31 + getAge();
        return hash;
    }
}

   

5. The relationship between the return value of hashCode() and equals() is as follows:

• If x.equals(y) returns "true", then the hashCode() of x and y must be equal.

• If x.equals(y) returns "false", then the hashCode() of x and y may or may not be equal.

The reason why these two rules are like this is actually very simple. Take HashSet as an example. HashSet can have one or more boxes, and there can be one or more unique meta objects in the same box (the HashSet contains must be a unique meta object). This example shows that a meta-object can have the same hashCode as a different meta-object. But a meta-object can only be equal to a meta-object with the same content. So these two rules must hold.

Sixth, the design of these two functions should be noted:

If the object type you design is not used for collection objects, then there is no need to design the handling of these two functions yourself. This is the correct object-oriented design method. Any functions that users can't use for a while should not be designed, so as not to cause trouble for future function expansion.

If you want to be creative when designing and do not follow the above two sets of rules, then I advise you not to do such fanciful things. I haven't encountered any developer who told me that designing these two functions should violate the two rules mentioned above. When I encounter these violations, they are all treated as design errors.

When an object type is used as the meta-object of a collection object, the object should have its own design for handling equals(), and/or handling hashCode(), and should comply with the two principles mentioned above. equals() first checks whether null and whether they are of the same type. Checking the same type is to avoid throwing exceptions such as ClassCastException. Checking for null is to avoid throwing exceptions such as NullPointerException.

If your object contains too much data, the two functions equals() and hashCode() will become inefficient. If the object has data that cannot be serialized, equals() may fail in its operation. Imagine an object x, one of its integer data is transient (cannot be serialized into a binary data stream). However, both equals() and hashCode() rely on this integer data. So, is this object the same before and after serialization? The answer is different. Because the integer data before serialization is valid data, after serialization, the value of this integer data is not stored, and after the binary data stream is converted into an object again, the state of the two (object before and after serialization) It's different. This is also to be noted.

Available at: http://www.importnew.com/25783.html, http://www.importnew.com/25783.html

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325191057&siteId=291194637