java hashCode()方法,equals()方法 ==

1、Object类的equals和hashCode方法

==比较两个对象的地址。只有a,b指向同一个对象,才返回true。非常严格,不能被重写。

equals默认和==效果相同。
但是可以重写equals方法。比如String类重写了,即使a,b指向的对象不是同一个,但是两个字符串长度字符都相同就返回true。

hashCode()默认比较地址的哈希码。一般ab指向不同对象时(地址一定不同),哈希码也不同。
因此:

a==b 必定有 a、b的hashCode相同
a、b的hashCode相同 不一定有 a==b

2、equals和hashCode方法的作用

自己用

自己使用的时候,只需要重写和使用equals方法就可以了。比如Point类{x,y}。重写equals方法只需要比较两个对象的x坐标和y坐标。
事实上,自己使用不一定要重写equals方法,新定义一个isEqual方法、same方法都能用。

当作HashSet、HashMap的键

HashSet内部也是HashMapHashMap怎么判断两个键相等?
目标是判断已有元素是否和当前元素equals,但是直接调用equals太费时间,因此先比较hashCode,这也是hashcode存在的原因。

如何快速比较两个对象是否equals:
若hashCode不同则必定不equals(这是由hashCode方法的特点保证,后面介绍),若hashCode相同,再调用equals

但是比较所有元素的hashCode需要o(n)时间,也很慢,所以hashmap内部是一个链表组成的数组。判断是否contains时,先把hashcode映射为数组的index,接着只需要比较index号的链表内是否存在具有相同hashCode的元素。

这样做也有一点点问题,看后文第六部分。

3、重写equals和HashCode方法

重写equals要求,这些要求是很显然的:

  1. 自反性 a.equals(a)
  2. 对称性 x.equals(y) y.equals(x)
  3. 一致性 x.equals(y) 多次调用结果一致
  4. 对于任意非空引用x,x.equals(null) 应该返回false

重写HashCode方法要求:

  1. equalshashCode必须相同。. hashCode不同,则必须不equals
  2. 尽量保证hashCode相同时equals,尽量保证不equals的时候,hashCode不同。
    也就是说重写后的equals和hashcode基本有一致性,因为hashCode的目的就是快捷版的equals

由于第1条的规定,重写了equals方法就必须重写hashCode方法。

4、数组的equals和hashCode

数组没有重写equals方法和hashcode方法,直接arr1.equals(arr2)比较的是地址。
所以比较两个数组是否长度相等且元素相等,使用Arrays.equals(arr1,arr2)方法,而不是arr1.equals(arr2)

因为没有重写equals方法和hashCode方法,数组不建议放进HashSet和HashMap的key。

5、list的equals和HashCode

list的equals比较的是list长度和每个元素是否equals。

list的元素和长度都相同,则equals,且hashCode相同。
list的元素和长度不都相同,则不equals,且大概率hashCode不相同。

因此,list执行完add前后肯定不equals,hashCode也大概率不同。

6、 对象做key后不要修改

以List为例,看看list做key后再add会发生什么。

HashSet<List<Integer>> set = new HashSet<List<Integer>>();
ArrayList<Integer> list = new ArrayList<>();
set.add(list);
System.out.println(list.hashCode());//1
System.out.println(set.iterator().next().hashCode());//1
list.add(8);

System.out.println(list.hashCode()); // 39
System.out.println(set.iterator().next().hashCode());// 39
System.out.println(set.iterator().next().equals(list));//true 
System.out.println(set.contains(list));   //false ???
set.add(list);
System.out.println(set.size());//2 ???
set.add(new ArrayList<Integer>());
System.out.println(set.size());//3

前面没有什么问题,但是set中已有的list和即将加入的listhashcode都是39,且 set.iterator().next().equals(list) 都返回true 了,为何set.contains(list)还是返回false

原因:
哈希表的内部是数组加链表(不考虑红黑树),即是链表组成的数组。添加元素时,先对元素计算哈希值hashcode,然后根据哈希值选择放入哪个链表(多个哈希值对应单个链表)。

第一次set.add的时候,list的hashcode是1,因此被放入某个table数组中的某一列,假设在第1列,即1号链表。
接着list调用list.add方法,改变了自己的hashcode为39。
第二次调用set.add方法,list的hashcode是39,尝试放入某一列,假设是7,即7号链表。和7号链表的所有元素比较(先比较hashcode,相同就再比较equals),没有发现冲突(因为7号链表没有其他元素),所以可以放入。
最后,set中拥有了两个指向同一个list的“指针”,虽然这两个list指向同一地址,但是没有冲突。

所以:list放入set之后,不要再调用list.add list.setlilst.remove方法了。


同样的道理,一般的对象放入set后,也不要让它的hashcode改变。例如

HashSet<Student> set = new HashSet<Student>();
Student s = new Student(2);    
set.add(s);
s.age=1;
set.add(s);
System.out.println(set.size());//2
class Student{
    int age;
    public Student(int age){
        this.age = age;
    }
    @Override
    public boolean equals(Object o){
        if(o==this) return true;
        if(o instanceof Student2){
            return ((Student2)o).age == this.age;
        }
        return false;
    }
    @Override
    public int hashCode(){
        return this.age;
    }
}

因为改变了对象sage,第二次add又成功了。此时打印set的两个元素,age都是1。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值