Equals and hashCode are both non-final methods on objects that are designed to be overridden, so they are often used in programming. However, it is necessary to master the coverage criteria of these two methods and their differences, and there are many related problems.
Let’s continue with an interview to test your knowledge of equals and hashCode.
Interviewer: It’s in Java= =
Equals = ();
The equals() function is used to determine whether two objects are equal. The Object definition is:
public boolean equals(Object obj) {
return (this == obj);
}
Copy the code
This means that equals is equivalent to == before we implement our own equals method, and the == operator determines whether two objects are the same object, i.e. whether their addresses are equal. Overriding equals is more about seeking the logical equality of two objects. You can say that the value is equal, or the content is equal.
In the following cases, you can achieve this goal by not overriding equals:
- Each instance of a class is essentially unique: it emphasizes the active entity and does not care about the value of, say, Thread. We care about which Thread, and we can compare using equals.
- It doesn’t matter if the class provides a logical equivalence test: users of some classes don’t use the comparison function, such as the Random class. Few people compare two Random values
- The superclass already overrides equals, and the subclass only needs to use the behavior of the superclass: for example, AbstractMap already overrides equals, and the subclass inherits the behavior of the superclass, which does not need to be implemented.
- If the class is private or package-level private, there is no use for equalsOverride equals () to disable it:
@Override public boolean equals(Object obj) { throw new AssertionError(); }
Interviewer: So do you know the rules for overriding equals?
I’ve seen this in Effective Java, if I remember correctly:
Reflexivity: x.equals(x) should return true for any non-null reference value x.
Symmetry: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
Transitivity: 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) should return true.
Consistency: Multiple calls to x.equals(y) always return true or false for any non-null reference values x and y, provided that the information used in the equals comparison on the object has not been modified.
Non-null: x.equals(null) should return false for any non-null reference value x.
Interviewer: Describe situations where symmetry and transitivity are violated
Symmetry violation
In many cases, we override equals so that our class is compatible with equals to a known class, such as in the following example:
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensiticeString)
return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
if (o instanceof String)
return s.equalsIgnoreCase((String) o);
return false; }}Copy the code
This is a good idea. We want to create a case-insensitive String that is also compatible with String as an argument. Suppose we create a CaseInsensitiveString:
CaseInsensitiveString cis = new CaseInsensitiveString("Case");
Copy the code
So there must be cis.equals(“case”), so the question is, “case”.equals(cis)? String is not CaseInsensiticeString compatible, so the equals of String does not take CaseInsensiticeString as an argument.
So there is a rule that when overriding equals, only variables of the same type are compatible.
Violation transitivity
The transitivity is that A is equal to B, B is equal to C, so A should also be equal to C.
Suppose we define a class Cat.
public class Cat()
{
private int height;
private int weight;
public Cat(int h, int w)
{
this.height = h;
this.weight = w;
}
@Override
public boolean equals(Object o) {
if(! (o instanceof Cat))return false;
Cat c = (Cat) o;
returnc.height == height && c.weight == weight; }}Copy the code
A cat is a good cat, no matter whether it is black or white and catches mice.
public class ColorCat extends()
{
private String color;
public ColorCat(int h, int w, String color)
{
super(h, w);
this.color = color;
}
Copy the code
We can use equals () to compare colors, but we don’t want to compare cats with the same type of variables. We can use equals () to compare cats with the same type of variables, and we can use equals () to compare cats with the same type of variables. We can use equals () to compare cats with the same type of variables.
@Override
public boolean equals(Object o) {
if (! (o instanceof Cat))
return false; // Not Cat or ColorCatfalse
if (! (o instanceof ColorCat))
returno.equals(this); // It's not a color cat, it must be a normal cat, ignore the color contrastreturnsuper.equals(o)&&((ColorCat)o).color.equals(color); // This is the time to compare colors}Copy the code
Suppose we define cats:
ColorCat whiteCat = new ColorCat(1,2, 1)"white"); Cat Cat = new Cat(1,2); ColorCat blackCat = new ColorCat(1,2, 2)"black");
Copy the code
WhiteCat is equal to cat, cat is equal to blackCat, but whiteCat is not equal to blackCat, so it does not meet the transitivity requirement.
So when overwriting equals, make sure you follow these 5 rules or you’ll always have trouble.
Interviewer: Do you have a knack for overriding equals at work, such as writing equals in a String?
Writing:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while(n-- ! = 0) {if(v1[i] ! = v2[i])return false;
i++;
}
return true; }}return false;
}
Copy the code
There are several tips for equals above:
- Use the == operator to check if the parameter is a reference to the object: if it is the object itself, it is returned directly, intercepting the call to itself, which is a performance optimization.
- Use the instanceof operator to check if the argument is of the correct type: if not, return false. As in the symmetry and transitivity example, don’t try to be compatible with other types. In practice, the type to check is either the type of the equals class or the type of the interface that that class implements, such as Set, List, or Map.
- Convert parameters to the correct type: After the previous test, it will almost certainly succeed.
- For key fields in the class, check whether the fields in the parameter are equal to the corresponding fields in the object: Fields of the basic type are used
= =
In the float field, we use the Float.compare method, and in the double field, we use the Double.compare method. For the other reference fields, we usually recursively call their equals method to compare them, plus the null check and the self reference check. We usually write this:(field == o.field || (field ! = null && field.equals(o.field)))
In the String above, we use an array, so we just compare each bit in the array. - After writing, consider whether the above mentioned symmetry, transitivity, consistency, etc.
A couple of other things to note.
Always override hashCode when overriding equals
The equals function must be of type Object
The equals method itself doesn’t have to be too smart. It just has to determine that some values are equal.
The interviewer, just mentioned hashCode, what’s the use?
HashCode is used to return the hash value of an Object, mainly for quick lookup, because hashCode is also found in objects, so all Java objects have hashCode, and in hash structures like Hashtables and HashMaps, All look up the position in the hash table by hashCode.
If two objects are equals, then their Hashcodes must be equal,
But hashCode is equal, equals is not necessarily equal.
In the case of HashMap, the chain-address method is used to handle hashes, assuming a hash table of length 8
0, 1, 2, 3, 4, 5, 6, 7Copy the code
So, when inserting data into a HashMap, the key is hashCode. Generally, hashCode%8 is used to get the index. If there are elements in the index, a linked list is used to continuously link more elements to the position. So what hashCode does is find the location of the index, and then use equals to compare the elements for equality, so to speak, find the bucket first, and then look inside it.
Interviewer: What tips do you know about overwriting hashCode?
The goal of a good hashCode method is to generate unequal hash codes for unequal objects. Similarly, equal objects must have equal hash codes.
A good hash function should distribute the instances uniformly across all hash values. In combination with previous experience, the following methods can be adopted:
From the Effective Java
-
Store a non-zero constant value, such as 17, in the result int;
-
For each key domain F (each domain designed in the equals method), do the following:
A. Calculate the hash code of type int for the field;
I. If the field is of type Boolean, compute (f? 1:0), ii. If the field is of type byte,char,short, or int, calculate (int)f, iii. If long, evaluate (int)(f^(f>>>32)).iv. If float is used, calculate float.FloatToIntBits (f).v. If it is a double, compute the double-.doubletolongBits (f), and then compute the hash value vi of long. If it is an object reference, the hashCode of the field is recursively called. If it is a more complex comparison, a paradigm needs to be computed for the field, and then hashCode is called against the paradigm, returning 0 vii if null. If it is an array, each element is treated as a separate field.Copy the code
b.result = 31 * result + c;
-
Returns the result
-
Write unit tests to verify that all equal instances have equal hash codes.
(b) The hash value of 31*result + c depends on the order of the field. (C) The hash value of 31*result + c depends on the order of the field. (D) The hash value of 31*result + c depends on the order of the field. A nice feature of 31 is that 31* I ==(I <<5)-i, that is, 2 ^ 5 minus 1, and the virtual machine will optimize the multiplication operation to shift operation.