要回答这个问题,我们应该先认识一下obj中的equals和hascode方法

1.equals()方法在obj中定义如下:

public boolean equals(Object obj) {
return (this == obj);
}

可以看到因为‘==’,所以是将两个对象的地址值进行比较(比较对象的引用是不是相同),但是在String和Integer等包装类中已经重写了equals()和hashcode()方法,比如在String类中:

public boolean equals(Object anObject) {
if (this == anObject) {
    return true;
}
if (anObject instanceof String) {
    String anotherString = (String)anObject;
    int n = count;
    if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
    if (v1[i++] != v2[j++])
return false;
}
return true;
    }
}
return false;
}

可以看出这不是对象地址的比较了,而是内容的比较,依此类推,其他包装类也是这样的

附加:重写equals()需要注意的地方:

• 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
• 反射性:x.equals(x)必须返回是“true”。
• 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
• 还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
• 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。

2.hashcode()方法在obj中定义如下:

public native int hashCode();

本地方法,与机器有关,但是它是将一个对象的内部地址转化成一个整数来实现的,在String等包装类中,hascode()方法也被重写了,例如String的Hascode():

public int hashCode() {
int h = hash;
if (h == 0) {
    int off = offset;
    char val[] = value;
    int len = count;

for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
}

可以看到String的hashcode()与对象的内容有关了,与地址无关了

简单总结一下:在obj中的equals()和hashcode()是原始的,没有被重写的,且二者都与对象的地址有关,在String等包装类中,equals()和hashcode()是被重写了的,与对象的内容有关

3.先看一下JDK中对hashcode的常协规定:

  • 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
  • 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能

总结一下:根据equals()对象相同的时候,hashcode()一定要相同,根据equals()对象不同的时候,hashcode()可以相同,可以不同

好了,既然两个方法我们都了解的差不多了,那么”为什么覆写equals()方法的时候总是要覆写hashcode()“???

首先,我认为hascode主要是为集合类服务的,下面以hashset为例子,那我们先来了解一下HashSet

HashSet:不允许出现重复对象,元素存放位置不确定

HashSet存放元素的原理:当向Hashset中增加元素的时候,首先计算元素的hashcode,根据hashcode得到一个位置来存放该元素,如果在该位置没有元素,那么集合认为该元素不存在与计划中,所以直接增加进去,如果该位置有一个元素,那么将要放进去的元素与该位置上面已经存在的元素进行equals比较,若返回flase,则集合认为该对象不在集合中,可加入,然后因为该位置已经存在元素,所以进行一次散列,得到一个新的地址,若得到的地址中还是存在元素,那么一直进行散列,直到找到的地址中没有元素为止,然后把需要增加的元素放入该地址中,若true,则集合认为该元素已经存在与集合中,不会将对象增加到集合中。

现在我们对HashSet有了一定的了解,那么继续”为什么覆写equals()方法的时候总是要覆写hashcode()“这个问题

假如我在构造一个类的时候,只重写了equals方法而没有重写hashcode()方法,在我不需要使用集合的时候可能看不出什么问题,但是一旦我需要使用集合,问题就大了,而且这个bug还很隐秘,举个栗子:

 import java.util.*;
public class HashSetTest
{
   public static void main(String[] args)
    {
                 HashSet hs=new HashSet();
                 hs.add(new Student(1,"zhangsan"));
                 hs.add(new Student(2,"lisi"));
                 hs.add(new Student(3,"wangwu"));
                 hs.add(new Student(1,"zhangsan"));
 
                 Iterator it=hs.iterator();
                 while(it.hasNext())
                 {
                        System.out.println(it.next());
                 }
     }
}
class Student
   {
     int num;
     String name;
     Student(int num,String name)
                {
                this.num=num;
                 this.name=name;
                 }
              public String toString()
                {
                    return num+":"+name;
                 }
           }     

输出结果:(结果输出序号无序的原因是Hashset无序储存)

1:zhangsan

1:zhangsan 

3:wangwu

2:lisi 

观察结果,我们使用的是HashSet,但是结果中出现了相同的元素,这是为什么呢?我们来观察一下,我只重写了equals(),重写的equals比较的是对象的内容,当有两个new Student(1,"zhangsan"))的时候,这是两个内容相同的不同地址的对象,使用equals比较的结果是True,当时我没有重写hashcode,而obj下的hashcode的取值与对象的地址有关,所以这两个对象的hashcode是不同的,因为他们的地址不同,所以在放入集合的时候,集合看到二者的hashcode不同,认为二者是两个不同的对象,所以集合中就有了两个重复的对象,分析完毕

解决方法:重写hascode()方法,继续上面的栗子:

 class Student
{
int num;
String name;
Student(int num,String name)
{
            this.num=num;
            this.name=name;
}
public int hashCode()
{
            return num*name.hashCode();
}
public boolean equals(Object o)
{
            Student s=(Student)o;
            return num==s.num && name.equals(s.name);
}
public String toString()
{
            return num+":"+name;
}
}

结果:(结果输出序号无序的原因是Hashset无序储存)

1:zhangsan
3:wangwu
2:lisi

可以看到输出结果中已经没有重复的元素了,这是因为我重写了hascode()方法,使得hashcode的取值只与对象的内容有关,而与对象的地址无关,所以集合比较hascode的时候看到二者的hascode相等,会认为二者是相同的对象,所以重复的对象不会被放进集合中

错误和不清楚的地方还请大家指出,我也是初学者,难免有错误的地方,欢迎大家一起讨论学习!!!!

欢迎拍砖!!!!!

--期末,下午,16:58

为什么覆写equals()方法的时候总是要覆写hashcode()?的更多相关文章

  1. [改善Java代码]覆写equals方法必须覆写hashCode方法

    覆写equals方法必须覆写hashCode方法,这条规则基本上每个Javaer都知道,这也是JDK API上反复说明的,不过为什么要这样做呢?这两个方法之间有什么关系呢?本建议就来解释该问题,我们先 ...

  2. [改善Java代码]覆写equals方法时不要识别不出自己

    建议45: 覆写equals方法时不要识别不出自己 我们在写一个JavaBean时,经常会覆写equals方法,其目的是根据业务规则判断两个对象是否相等,比如我们写一个Person类,然后根据姓名判断 ...

  3. java覆写equals方法

    何时需要重写equals() 当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念). object规范规定,如果要重写equals(),也要重写hashcode() 如何覆写equals() ...

  4. 覆写equals方法为什么需要覆写hashCode方法

    覆写equals方法必须覆写hashCode方法,是JDK API上反复说明的,不过为什么要这样做呢?这两个方法之间有什么关系呢? void test() { // Person类的实例作为Map的k ...

  5. 两个对象用equals方法比较为true,它们的Hashcode值相同吗?

    两个对象用equals方法比较为true,它们的Hashcode值相同吗? 答:不一定相同.正常情况下,因为equals()方法比较的就是对象在内存中的值,如果值相同,那么Hashcode值也应该相同 ...

  6. 8.2.3 覆写 Equals

    经过对四种不同类型判等方法的讨论,我们不难发现不管是 Equals 静态方法.Equals 虚方法 抑或==操作符的执行结果,都可能受到覆写 Equals 方法的影响.因此研究对象判等就必须将注意 力 ...

  7. java提高篇(十三)-----equals()方法总结

    equals() 超类Object中有这个equals()方法,该方法主要用于比较两个对象是否相等.该方法的源码如下: public boolean equals(Object obj) { retu ...

  8. java重写equals方法

    @Override public int hashCode() { return task.getId(); } @Override public boolean equals(Object obj) ...

  9. java对象equals方法的重写

    根类Object中的equals方法描述: public boolean equals(Object obj)The equals method for class Object implements ...

随机推荐

  1. 转: .Net 4.0 ExpandoObject 使用

    本篇文章中就ExpandoObject的基本使用进行一些demo.我们几乎都知道dynamic特性是.net 4.0中一个主要的新特性,而ExpandoObject正是这样的一个动态的类型.Expan ...

  2. Celery 源码解析七:Worker 之间的交互

    前面对于 Celery 的分布式处理已经做了一些介绍,例如第五章的 远程控制 和第六章的 Event机制,但是,我认为这些分布式都比较简单,并没有体现出多实例之间的协同作用,所以,今天就来点更加复杂的 ...

  3. java参数传值方式

     java参数有值类型和引用类型两种.所以java参数的传值也就从这两个方面分析. 从内存模型来说参数传递更为直观一些,这里涉及到两种类型的内存:栈内存(stack)和堆内存(heap).   基本类 ...

  4. jenkins学习之自动打包构建nodejs应用

    上一节记录了下jenkins在centos下的安装,这节继续,说下怎么使用jenkins和nodejs进行自动打包更新服务. 创建任务 创建任务比较简单,这里我们创建自由风格项目: General信息 ...

  5. mysql 5.7.13 安装配置方法图文教程(linux) (转)

    http://www.jb51.net/article/87160.htm ************************************************ linux环境Mysql ...

  6. TFboy养成记 CNN

    1/先解释下CNN的过程: 首先对一张图片进行卷积,可以有多个卷积核,卷积过后,对每一卷积核对应一个chanel,也就是一张新的图片,图片尺寸可能会变小也可能会不变,然后对这个chanel进行一些po ...

  7. AbpZero双重认证之短信的坑

    一.什么是双重认证 所谓双重认证简单来说就是除了用户名密码方式外,还额外增加了一道登录屏障.登录时先输入用户名和密码,正确后会向邮箱或手机号发送一个验证码(取决于您采用何种方式,甚至可以采用银行的电子 ...

  8. 2718:晶晶赴约会-poj

    总时间限制:  1000ms 内存限制:  65536kB 描述 晶晶的朋友贝贝约晶晶下周一起去看展览,但晶晶每周的1.3.5有课必须上课,请帮晶晶判断她能否接受贝贝的邀请,如果能输出YES:如果不能 ...

  9. Android自定义指示器时间轴

    指示器时间轴在外卖.购物类的APP里会经常用到,效果大概就像下面这样,看了网上很多文章,大都是自己绘制,太麻烦,其实通过ListView就可以实现.   在Activity关联的布局文件activit ...

  10. js中对节点属性的操作和对节点的操作

    常用的节点属性操作方法 1.setAttribute(name,value):给某个节点添加一个属性 2.getAttribute(name):获取某个节点属性的值. 3.removeAttribut ...