目录
equals 和 hashCode 这对老基友
今天读到《Effective Java》第二章的 equals 和 hashCode,
在多抓鱼买的二手的。。。
我寻思这块也算是个老大难,
经常使用不适当甚至使用不错,
又是Object的方法,
也是面试官贼喜欢问的,
比如equals相等的,hashCode相等嘛?
hashCode相等的,equals相等嘛?
也可能会搭配着HashMap来问
他们倆有哪些不可割舍的前世今生,使用他们需要注意些什么呢?
让我们一起去看看这对基友的情感纠纷
如果你觉得已经很熟悉这对基友了,甚至精通,请直接跳过本文 【溜了】
先谈 Object
Java中所有的类都有一个公共的祖先 Object
java.lang.Object
java lang包下的爹
而Java类并没有显示的去继承Object
为啥呢?
我寻思既然全世界都知道你是所有人的爹,就没有必要人人在贴个标签说你是他爹
Object
方法一览
package java.lang;
/**
* Class {@code Object} is the root of the class hierarchy.
* Every class has {@code Object} as a superclass. All objects,
* including arrays, implement the methods of this class.
*
* @author unascribed
* @see java.lang.Class
* @since JDK1.0
*/
public class Object {
private static native void registerNatives();
static {
registerNatives();
}
public final native Class<?> getClass();
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
protected native Object clone() throws CloneNotSupportedException;
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
protected void finalize() throws Throwable { }
}
registerNatives
一个本地方法,具体是用C(C++)在DLL中实现的,然后通过JNI调用
getClass
返回此 Object 的运行时类
像wait、notify、finalize一般都会问
而本文我们的重点在 equals 和 hashCode
先提前剧透一下
我们看到hashCod是一个native方法
equals除非是同一个实例都返回false (this == obj)
再啰嗦 ==
这个 ==
我们也经常用到
讲equals我们再做个铺垫
==
的用法:
- 值类型(
int
,char
,long
,boolean
等)判断值的相等性 - 对象引用 判断的是引用所指的对象是否是同一个
栈中的引用指向堆中的对象实例
接下来高能预警。。。
“逻辑相等” equals
我们把Object
的equals单搞出来
public boolean equals(Object obj) {
return (this == obj);
}
上文我们说了除非一个对象的同一个实例都返回false
什么叫一个对象的实例,类的每个实例本质上都是唯一的
Object obj1 = new object();
//生成了一个实例
Object obj1 = new object();
//生成了另一个实例
调用equals则返回false
然而在实际的业务场景我们并不是equals在同一实例下才返回false,
在一些场景下我们是属性相同就返回true
我们先来看下String 对 equals 的覆盖(虽然开发规约不推荐行尾注释,但为了方便我就…)
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数组是否相同
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;
}
比如我们一个Person类,覆盖equals方法
只要身份证号相同我们就返回true,
或者姓名、年龄都相同返回true,
根据业务场景来选择判断相同的条件
但大致步骤都是:
1,判断是否为一个对象的引用
2,判断类型
3,向下转型
4,被选中为判断依据的属性是否相等
equals的性质
- 自反性
x.equals(x)
- 对称性
x.equals(y),则y.equals(x)
- 传递性
x.equals(y)且y.equals(z),则x.equals(z)
- 一致性
两个对象相等,他们就必须始终保持相等,除非他们中有对象被修改
- 非空性
所有的对象保证不是null
不需要判空操作
instanceof已经足够
o instanceof MyClass
已经排除了空值问题
不要为了equals的通用性而违背了上述的某一个性质
不要尝试将两个不同的类的实例强行equals
当然了,写完的equals方法要去多测试几波,说不定就会超出你的相信,因此不要想当然也
再剧透一波,重写equals方法,也一定要重写hashCode,哎,扯不断的基情啊
hashCode 我就是个生成整数的
hashCode是为当前对象生成一个整形数值
同一个对象一定返回同样的hashCode
不一个的对象不一定产生不同的hashCode
public native int hashCode();
覆盖了equals,也一定要覆盖hashcode方法,上面我们已经说了,再强调一遍
如果两个对象equals方法比较是相等的,那么hashCode产生同样的整数结果;
但是如果两个对象equals方法比较是不相等的,hashCode不一定不相等
换句话说hashCode相不相等决定不了equals的结果
给不相等的对象产生截然不同的整数结果,可以提高散列表性能,但也并不代表不相等的对象一定产生不同的hashCode
但相等的对象必须有相同的hashCode
试想如果给相等的对象产生不同的hashCode,就会把相等的对象打散到散列表的不同地方
这显然是我们不想看到的
如果你觉得比较绕,那就多读几遍,再想想就明白了
我觉得hashCode主要是为了散列表(如HashMap)设计的,
话外音:如果说你没有使用散列表,你覆盖了equals,也可以不覆盖hashCode(我是这样觉得哈)
HashMap 早已饥渴难耐
我们来看看HashMap的equasl方法和hashCode方法
public final int hashCode() {
//key的hashCode和value 的hashCode取异或,尽量打散
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
// 只有key和value都相等才相等
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
equals的判断关键域是key和value,key相等、value相等,才是相等
根据我们上面提到的equals相等那么hashCode也相等
hashCode(key)相等、hashCode(value)相等
那么hashCode(key) + hashCode(value) 也必然相等
HashMap的做法刚好验证了我们上面所说
我们再看下java.util.Objects#equals
注意:这里是Objects 多了个s
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
最终会用这个散列表存的key,value所属类的equals方法来判断
Person:
1)equals是根据name判断的
2)没有重写hashCode
HashMap<Person,Integer> map
Integer存的是年龄
map.set(new Person(“wmx”),22)后,再get(new Person(“mwx”)),取到的是空,为啥?
get的时候也是需要先得到根据key得到hashCode
由于hashCode没有重写,导致相等的对象得到不相同的hashCode,
从而打到散列表不同的位置,因此取不到值
转载请注明:汪明鑫的个人博客 » 梦回 equals 和 hashCode
说点什么
您将是第一位评论人!