分享

Java 重写Object类的常见方法-equals和hashCode

 dabinglibrary 2019-08-16

转:https://www.jianshu.com/p/55626bec6b89

在java 中Object是一个具体的类,但是他的设计主要是为了拓展。当我们写一个类的时候,都会对Java.lang.Object类的一些重要方法进行重写( 改写 override),这些方法包含:hashCode(),toString(),equals(),finalize(),clone(),wait(),notify()/notifyAll() 这八个方法。

这里将总结介绍这些基本的方法,并对这些方法的改写提供总结。

1 equals方法改写

java中==和eqauls()的区别

==是运算符,用于比较两个变量是否相等,而equals是Object类的方法,用于比较两个对象是否相等。默认Object类的equals方法是比较两个对象的地址,此时和==的结果一样。换句话说:基本类型比较用==,比较的是他们的值。默认下,对象用==比较时,比较的是内存地址,如果需要比较对象内容,需要重写equal方法。

下面来理解一下:如果需要比较对象内容,需要重写equal方法。

可以看到Object的equals方法实现,默认Object类的equals方法是比较两个对象的地址,此时和==的结果一样。
String 进行了重写从而实现了字符串的比较。那么下方这里字符串比较肯定返回true了。

 public static void main(String[] args) {        String str1 = new String("123");        String str2 = new String("123");
        System.out.println(str1.equals(str2));  //true
    }
实现高质量的equals方法的诀窍包括
  1. 使用==操作符检查“参数是否为这个对象的引用”;

  2. 使用instanceof操作符检查“参数是否为正确的类型”;

  3. 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;

  4. 编写完equals方法后,问自己它是否满足对称性、传递性、一致性;

  5. 重写equals时总是要重写hashCode;

  6. 不要将equals方法参数中的Object对象替换为其他的类型,在重写时不要忘掉@Override注解。

理解这几个步骤,直接参考源代码Set,list和map的父类的源代码的equals方法的比较:

重写equals 应该遵守的约定

自反性(x.equals(x)必须返回true);
对称性(x.equals(y)返回true时,y.equals(x)也必须返回true);
传递性(x.equals(y)和y.equals(z)都返回true时,x.equals(z)也必须返回true);
一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值);
非空性(对于任何非null值的引用x,x.equals(null)必须返回false)。

2 hashCode方法改写

此方法返回对象的哈希码值,什么是哈希码?

哈希码产生的依据:哈希码并不是完全唯一的,它是一种算法,让同一个类的对象按照自己不同的特征尽量的有不同的哈希码,但不表示不同的对象哈希码完全不同。也有相同的情况,看程序员如何写哈希码的算法。

简单理解就是一套算法算出来的一个值,且这个值对于这个对象相对唯一。哈希算法有一个协定:在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行hashcode比较时所用的信息没有被修改。(ps:要是每次都返回不一样的,就没法玩儿了)

两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同。Java对于eqauls方法和hashCode方法是这样规定的:(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;(2)如果两个对象的hashCode相同,它们并不一定相同。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)

可以在hashcode中使用随机数字吗?

不行,因为同一对象的 hashcode 值必须是相同的

重写equals方法的时候为什么需要重写hashcode

首先来看一段代码:

public class HashMapTest {
    private int a;    public HashMapTest(int a) {        this.a = a;
    }    public static void main(String[] args) {
        Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
        HashMapTest instance = new HashMapTest(1);        map.put(instance, 1);
        Integer value = map.get(new HashMapTest(1));        if (value != null) {
            System.out.println(value);
        } else {
            System.out.println("value is null");
        }
    } 

}//程序运行结果: value is null

简单说下HashMap的原理,HashMap存储数据的时候,是取的key值的哈希值,然后计算数组下标,采用链地址法解决冲突,然后进行存储;取数据的时候,依然是先要获取到hash值,找到数组下标,然后for遍历链表集合,进行比较是否有对应的key。比较关心的有2点:1.不管是put还是get的时候,都需要得到key的哈希值,去定位key的数组下标; 2.在get的时候,需要调用equals方法比较是否有相等的key存储过。
  反过来,我们再分析上面那段代码,Map的key是我们自己定义的一个类,可以看到,我们没有重写equal方法,更没重写hashCode方法,意思是map在进行存储的时候是调用的Object类中equals()和hashCode()方法。为了证实,我们打印下hashCode码。

public class HashMapTest {
    private Integer a;    public HashMapTest(int a) {        this.a = a;
    }    public static void main(String[] args) {
        Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
        HashMapTest instance = new HashMapTest(1);
        System.out.println("instance.hashcode:" + instance.hashCode());        map.put(instance, 1);
        HashMapTest newInstance = new HashMapTest(1);
        System.out.println("newInstance.hashcode:" + newInstance.hashCode());
        Integer value = map.get(newInstance);        if (value != null) {
            System.out.println(value);
        } else {
            System.out.println("value is null");
        }
    }
}//运行结果://instance.hashcode:929338653//newInstance.hashcode:1259475182//value is null

不出所料,hashCode不一致,所以对于为什么拿不到数据就很清楚了。这2个key,在Map计算的时候,可能数组下标就不一致,就算数据下标碰巧一致,根据前面,最后equals比较的时候也不可能相等(很显然,这是2个对象,在堆上的地址必定不一样)。我们继续往下看,假如我们重写了equals方法,将这2个对象都put进去,根据map的原理,只要是key一样,后面的值会替换前面的值,接下来我们实验下:

public class HashMapTest {    private Integer a;    public HashMapTest(int a) {        this.a = a;
    }    public static void main(String[] args) {
        Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
        HashMapTest instance = new HashMapTest(1);
        HashMapTest newInstance = new HashMapTest(1);
        map.put(instance, 1);
        map.put(newInstance, 2);
        Integer value = map.get(instance);
        System.out.println("instance value:"+value);
        Integer value1 = map.get(newInstance);
        System.out.println("newInstance value:"+value1);

    }    public boolean equals(Object o) {        if(o == this) {            return true;
        } else if(!(o instanceof HashMapTest)) {            return false;
        } else {
            HashMapTest other = (HashMapTest)o;            if(!other.canEqual(this)) {                return false;
            } else {
                Integer this$data = this.getA();
                Integer other$data = other.getA();                if(this$data == null) {                    if(other$data != null) {                        return false;
                    }
                } else if(!this$data.equals(other$data)) {                    return false;
                }                return true;
            }
        }
    }    protected boolean canEqual(Object other) {        return other instanceof HashMapTest;
    }    public void setA(Integer a) {        this.a = a;
    }    public Integer getA() {        return a;
    }
}//运行结果://instance value:1//newInstance value:2

你会发现,不对呀?同样的一个对象,为什么在map中存了2份,map的key值不是不能重复的么?没错,它就是存的2份,只不过在它看来,这2个的key是不一样的,因为他们的哈希码就是不一样的,可以自己测试下,上面打印的hash码确实不一样。那怎么办?只有重写hashCode()方法,更改后的代码如下:

public class HashMapTest {    private Integer a;    public HashMapTest(int a) {        this.a = a;
    }    public static void main(String[] args) {
        Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
        HashMapTest instance = new HashMapTest(1);
        System.out.println("instance.hashcode:" + instance.hashCode());
        HashMapTest newInstance = new HashMapTest(1);
        System.out.println("newInstance.hashcode:" + newInstance.hashCode());
        map.put(instance, 1);
        map.put(newInstance, 2);
        Integer value = map.get(instance);
        System.out.println("instance value:"+value);
        Integer value1 = map.get(newInstance);
        System.out.println("newInstance value:"+value1);

    }    public boolean equals(Object o) {        if(o == this) {            return true;
        } else if(!(o instanceof HashMapTest)) {            return false;
        } else {
            HashMapTest other = (HashMapTest)o;            if(!other.canEqual(this)) {                return false;
            } else {
                Integer this$data = this.getA();
                Integer other$data = other.getA();                if(this$data == null) {                    if(other$data != null) {                        return false;
                    }
                } else if(!this$data.equals(other$data)) {                    return false;
                }                return true;
            }
        }
    }    protected boolean canEqual(Object other) {        return other instanceof HashMapTest;
    }    public void setA(Integer a) {        this.a = a;
    }    public Integer getA() {        return a;
    }    public int hashCode() {        boolean PRIME = true;        byte result = 1;
        Integer $data = this.getA();        int result1 = result * 59 + ($data == null?43:$data.hashCode());        return result1;
    }
}//运行结果://instance.hashcode:60//newInstance.hashcode:60//instance value:2//newInstance value:2

可以看到,他们的hash码是一致的,且最后的结果也是预期的。
曾经同事趟过这坑,Map中存了2个数值一样的key,所以大家谨记哟! 在重写equals方法的时候,一定要重写hashCode方法。

作者:安东尼_Anthony
链接:https://www.jianshu.com/p/55626bec6b89
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多