一、JDK源码中的建议
(1)、equals()的使用
我们重写equals方法大部分是因为要将自定义bean作为HashMap的key时。 我们接下来看看HashMap中常用的几个用到equals()的地方。
- boolean containsKey(Object key);
containsKey(Object key)
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
hash(Object key)
//根据对象的hashCode()方法返回一个int
static final int hash(Object key) {
int h;
//可以从这里看出,HashMap允许key为null。
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
getNode(int hash, Object key)
这段代码Josh Bloch(超级牛,感兴趣可以百度,可以说没有他,就没有这么优秀的作品)是作者之一,他的风格一向如此,喜欢在逻辑运算符上赋值,所以初看可能比较费劲。 注意看红框里的都是赋值操作,然后用赋的值本身去做逻辑运算。 我们可以看到判断一个key是否存在,(去掉一些边界判断) 首先查看hash()值是否相等,然后查看对象是否地址相同(即同一个对象)或调用equals()查看是否相等。
2.V get(Object key);
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
可以看到这个调用了刚才上面的代码。
3.public V put(K key, V value) ;
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
可以看到如出一辙的风范,这里的代码我们仅指出此次我们研究的重点步骤。
1.查看集合是否为空,如果为空开始扩容,然后new一个节点插入该集合。
2.查看给定key的hash()位置(HashMap是一个数组,每个元素放置在tab[(length - 1) & hash]位置,如果该位置已有元素则通过链表或树继续添加)是否有元素,如果没有new一个节点放入该位置。
3.如果该位置已经存在元素,则查看该位置的key值和要插入的key值是不是同一个对象(首先判断hash()值,然后判断是否是同一个对象即看==返回值,或者equals()方法返回true),因为null==null,所以可以看出HashMap中只允许一个键为null。如果是同一个对象更新value值,如果不是进行遍历(链表或树),直到找到同一个key值,更新value,或者插入新节点(k,v)。
我们总结一下,在HashMap中很多时候用到了判断两个key是否相等。他们的规则如下:
如果hashCode()不同,两个对象不同(所以说如果我们要重写equals()方法来自定义两个对象是否相等,必须要重写hashCode()方法,因为首先判断的是hash值。)
如果hashCode()相同,再查看==||equals(),如果==||equals()返回true则表示是同一个对象。
(2)、equals()重写的五点建议
/**
* Indicates whether some other object is "equal to" this one.
* <p>
* The {@code equals} method implements an equivalence relation
* on non-null object references:
* <ul>
* <li>It is <i>reflexive</i>: for any non-null reference value
* {@code x}, {@code x.equals(x)} should return
* {@code true}.
* <li>It is <i>symmetric</i>: for any non-null reference values
* {@code x} and {@code y}, {@code x.equals(y)}
* should return {@code true} if and only if
* {@code y.equals(x)} returns {@code true}.
* <li>It is <i>transitive</i>: for any non-null reference values
* {@code x}, {@code y}, and {@code z}, if
* {@code x.equals(y)} returns {@code true} and
* {@code y.equals(z)} returns {@code true}, then
* {@code x.equals(z)} should return {@code true}.
* <li>It is <i>consistent</i>: for any non-null reference values
* {@code x} and {@code y}, multiple invocations of
* {@code x.equals(y)} consistently return {@code true}
* or consistently return {@code false}, provided no
* information used in {@code equals} comparisons on the
* objects is modified.
* <li>For any non-null reference value {@code x},
* {@code x.equals(null)} should return {@code false}.
* </ul>
* <p>
* The {@code equals} method for class {@code Object} implements
* the most discriminating possible equivalence relation on objects;
* that is, for any non-null reference values {@code x} and
* {@code y}, this method returns {@code true} if and only
* if {@code x} and {@code y} refer to the same object
* ({@code x == y} has the value {@code true}).
* <p>
* Note that it is generally necessary to override the {@code hashCode}
* method whenever this method is overridden, so as to maintain the
* general contract for the {@code hashCode} method, which states
* that equal objects must have equal hash codes.
*
* @param obj the reference object with which to compare.
* @return {@code true} if this object is the same as the obj
* argument; {@code false} otherwise.
* @see #hashCode()
* @see java.util.HashMap
*/
public boolean equals(Object obj) {
return (this == obj);
}
在注释上使用ctrl+shifgt+空格,就会出现文档注释预览(idea2020版本做了更好的优化)
这个方法用来指示另一个对象是否"equals to"本对象。该方法实现对非空对象引用的一个等价关系:
反身性: 对于任意非空引用x,x.quals(x)应该返回true。
对称性:对于任意非空引用值{x}和{y} , x.equals(y)应返回true当且仅当y.equals(x)返回true 。
传递性:对于任意非空引用值x , y ,和z ,如果x.equals(y)返回true和y.equals(z)返回true ,那么x.equals(z)应该返回true 。
一致性:对于任意非空引用值x和y ,如果没有修改equals()方法中设置的信息。多次调用x.equals(y)应该始终返回true或始终返回false ,
对于任意非空引用x,x.equals(null)应该返回false 。
请注意,每当重写hashCode方法时,通常都需要重写该方法,以便维护hashCode方法的一般约定,该约定声明相等的对象必须具有相等的hash代码。
二、JDK源码中的优秀示例
(1)、String
public boolean equals(Object anObject) {
//如果地址相同,直接返回true
if (this == anObject) {
return true;
}
//判断是否是String的实例(值得注意的是null不是任何对象的实例,也就是说如果传入参数为null,直接返回false)
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;
}
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
//根据字符的unicode值,以31为基生成hashCode
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
(2)、自定义bean模仿
class Student {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof Student) {
Student student = (Student) o;
if (student.name != null && student.name.equals(name)) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return name.hashCode();
}
}