java的equals和hashcode方法的区别和联系

List和Set都是集合Collection的两个类,但是它们又有不同,最主要的一点就是List允许加入两个相同的元素,而Set不允许加入两个相同的元素。那么它们内部究竟是怎么判断元素不同的呢?说到这,就有必要先了解java中Object的equals和hashcode方法了。
由于Object是所有类的父类,所以java类默认都有这两个方法。
一般我们在定义类的时候都不会覆盖它们,当然这没什么问题,但有些情况下需要重写它们。先来看个例子吧。
package com.Howard.test01;
/**
 * 书
 * @author Howard
 * 2017年2月24日
 */
public class Book {
     /**
      * 书名
      */
     private String name;
     /**
      * 页数
      */
     private int  pageNum;
     public String getName() {
           return name;
     }
     public void setName(String name) {
           this.name = name;
     }
     public int getPageNum() {
           return pageNum;
     }
     public void setPageNum(int pageNum) {
           this.pageNum = pageNum;
     }
  
}
测试类:
package com.Howard.test01;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class EqualsTest {
     public static void main(String[] args) {
           Book b1 = new Book();
           Book b2 = new Book();

           b1.setName("java编程思想");
           b1.setPageNum(880);

           b2.setName("java编程思想");
           b2.setPageNum(880);

           List<Book> list = new LinkedList<>();
           Set<Book> set = new HashSet<>();

           list.add(b1);
           list.add(b2);
           set.add(b1);
           set.add(b2);

           System.out.println(b1.equals(b2));
           System.out.println(b1==b2);
           System.out.println(b1.hashCode());
           System.out.println(b2.hashCode());
           System.out.println("list:"+list.size());
           System.out.println("set:"+set.size());
     }
}
运行结果:
false
false
366712642
1829164700
list:2
set:2

可以看到:此时equals比较出来的结果是false,即它们并不是同一个元素。默认的equals方法是根据对象的内存地址是否一样来判断的,很明显,这里是分别new出的实例对象,故返回false。
这时我们为Book重写equals(eclipse可以右键source—>Generate Hashcode and equals自动生成,这里先重写equals)
package com.Howard.test01;
/**
 * 书
 * @author Howard
 * 2017年2月24日
 */
public class Book {
     /**
      * 书名
      */
     private String name;
     /**
      * 页数
      */
     private int  pageNum;
     public String getName() {
           return name;
     }
     public void setName(String name) {
           this.name = name;
     }
     public int getPageNum() {
           return pageNum;
     }
     public void setPageNum(int pageNum) {
           this.pageNum = pageNum;
     }
     @Override
     public boolean equals(Object obj) {
           if (this == obj)
                return true;
           if (obj == null)
                return false;
           if (getClass() != obj.getClass())
                return false;
           Book other = (Book) obj;
           if (name == null) {
                if (other.name != null)
                     return false;
           } else if (!name.equals(other.name))
                return false;
           if (pageNum != other.pageNum)
                return false;
           return true;
     }

}
再次运行上面的测试类:
true
false
366712642
1829164700
list:2
set:2
可以看出:此时equals返回的是true,当我们重新覆盖了equals后,具体的返回值就要看具体实现equals的逻辑是怎么写的了。按上面写的,就是根据对象属性的值是否一样来判断(一般eclipse为我们自动重构的equals方法也是这么判断的)

现在我们把重写的equals注释掉,重写hashcode方法(同样可以用eclipse帮我们生成)
package com.Howard.test01;
/**
 * 书
 * @author Howard
 * 2017年2月24日
 */
public class Book {
     /**
      * 书名
      */
     private String name;
     /**
      * 页数
      */
     private int  pageNum;
     public String getName() {
           return name;
     }
     public void setName(String name) {
           this.name = name;
     }
     public int getPageNum() {
           return pageNum;
     }
     public void setPageNum(int pageNum) {
           this.pageNum = pageNum;
     }
     @Override
     public int hashCode() {
           final int prime = 31;
           int result = 1;
           result = prime * result + ((name == null) ? 0 : name.hashCode());
           result = prime * result + pageNum;
           return result;
     }
}
重写运行测试类:
false
false
1783492
1783492
list:2
set:2
这时看到,原本b1和b2的hashcode一直不一样的,现在一样了。但此时set和list里面的元素数量都仍然是2,即set还是把它们当作不同元素。
那么接下来重写加上equals的方法:
package com.Howard.test01;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class EqualsTest {
     public static void main(String[] args) {
           Book b1 = new Book();
           Book b2 = new Book();

           b1.setName("java编程思想");
           b1.setPageNum(880);

           b2.setName("java编程思想");
           b2.setPageNum(880);

           List<Book> list = new LinkedList<>();
           Set<Book> set = new HashSet<>();

           list.add(b1);
           list.add(b2);
           set.add(b1);
           set.add(b2);

           System.out.println(b1.equals(b2));
           System.out.println(b1==b2);
           System.out.println(b1.hashCode());
           System.out.println(b2.hashCode());
           System.out.println("list:"+list.size());
           System.out.println("set:"+set.size());
     }
}
再一次运行测试类:
true
false
1783492
1783492
list:2
set:1
可以看到:b1和b2hash值一样,但是set却不把它们当作不同元素了。原来set在判断相同元素时,先调用的equals进行比较,由于我们重写了equals方法,所以这时候是根据属性值比较,而b1和b2属性值是相等的,所以此时set把它们当作相同的元素,并没用添加成功。
但是我们刚才在未重写hashcode,重写了equals是list和set的元素数量却仍然是2,这是为什么呢?其实set在比较元素时,如果发现equals相等,那么就会当作相同元素处理,如果发现不相等,那么还会去调用hashcode进行比较,只有equals和hashcode都返回false,set才把它当作不同的元素。这一点,我们可以看看hashset的add方法的源码便一目了然了(这里我使用的jdk版本是7)。

所以我们一般在重写equals方式时,都会一并重写了hashcode方法。
正因为此,在我们把元素add进集合后,最好不要再改变元素属性的值,如果需要,先取出来修改再放进去。为什么这么说呢,看看下面的例子就知道了
(我也是在看到张孝祥老师的视频后才知道有这么回事)
package com.Howard.test01;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class EqualsTest {
     public static void main(String[] args) {

           Book b1 = new Book();

           b1.setName("java编程思想");
           b1.setPageNum(880);

           Set<Book> set = new HashSet<>();
           System.out.println("改变前:"+b1.hashCode());
           set.add(b1);
           System.out.println("set:"+set.size());
           b1.setPageNum(770);
           System.out.println("改变后:"+b1.hashCode());
           set.remove(b1);
           System.out.println("set:"+set.size());
     }
}
结果:
改变前:417483024
set:1
改变后:1248881984
set:1

结果看到:当我们尝试着取出set里面的元素b1,发现并没有用,b1的hashcode改变,set根据hashcode根本不能将其移除。这就导致了内存泄露。
最后总结下:默认的equals方法是根据内存地址判断,hashset的add方法在比较元素时,如果发现equals相等,那么就会当作相同元素处理,如果发现不相等,那么还会去调用hashcode进行比较,只有equals和hashcode都返回false,才把它们当作不同的元素。当我们在重写equals时一般也把hashcode重写(因为一般我们重写也是为了根据对象属性判断相等而不是内存地址)。所以一般有这么个规则:如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等 如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。反之, 如果两个对象hashcode相等,他们不一定equals, 如果两个对象hashcode不相等,他们一定不equals。 在元素加进集合后,不要试图去改变元素属性的值。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值