Welcome everyone

梦回 equals 和 hashCode

java 汪明鑫 17浏览 0评论

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

喜欢 (0)

说点什么

您将是第一位评论人!

提醒
avatar
wpDiscuz