Java中equals相关
equals()与==的关系
==:它是一个操作符,作用是判断两个对象的地址是否相等
基础类型比较的是值,引用类型比较的是内存地址
equals():
- 类未覆盖equals()方法,则调用Object类提供的equals(),等价于通过“==”进行比较
- 类覆盖equals()方法,为了比较两者内容是否相等,如相等则返回true
equals()即使被覆盖,通常第一步也是利用"=="进行判断,若为false,再根据不同类型对值进行判断
举例
public static void main(String[] args) {
String var1 = new String("asd");
String var2 = new String("asd");
String var3 = "asd";
String var4 = "asd";
System.out.println(var1.equals(var2));
System.out.println(var1 == var2);
System.out.println(var1 == var3);
System.out.println(var3 == var4);
}
输出
说明:
从上面,可以看到String类重写了equals(),因为它可以判断字符串内容是否相等返回true或false。
同时,可以注意到,String类有两种实例化方式,及直接赋值和new,new方法是直接在堆中创建一个对象,而String的赋值先检查常量池中是否有相同的字符串常量,如果有则让String变量指向此字符串常量地址,否则在常量池中创建一个字符串常量。
所以,看到String直接赋值的变量比较地址时是返回true的
equals()和hashCode()
首先,我们要记住,不管是否运用到Key-Value集合中,重写equals()一定要重写hashCode(),因为这是一个设计规范,你永远不知道这个类型未来是否会运用到Key-Value中。记住之后,下面我们来详细讲解原理上的原因
hashCode()介绍
- hashCode()的作用是获取哈希码(散列码),返回类型是一个整数。哈希码是用来获取对象在哈希表的索引位置的(如,在hashMap中就是利用哈希码与哈希表(数组结构)的长度的取模运算得到索引位置)。
- hashCode()是定义在Object.java中,及所有类都包含它。它主要在类充当散列表类(hashMap,hashtable)中Key值或散列表类(hashSet)中元素时,起到判重的作用。
hashCode()的意义
比如在HashSet集合中,由于其不可重复特性,在检测重复时,利用率hashCode
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与该位置其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 加入对象所属类中的equals()方法来检查加入对象和集合中 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。这样可以在equals()之前先判断一次,及筛选一次,减少equals()b比较次数,提高效率。
HashSet是基于HashMap实现的其判重操作也是调用的HashMap中put()
hashCode()和equals()的关系
从逻辑上讲,相等对象的哈希值一定相等,同时由于不相等对象的哈希值也可能相同及哈希碰撞。所以我们可以得到一个一般规律:当对象,equals()相等时,hashCode()返回值一定相等,equals()不等时,hashCode()不一定不等。
所以,在类被应用到散列表类型时(HashMap,Hashtable,HashSet),我们希望可以通过相等的对象获取到对于的value和相等的对象在HashSet集合中不重复,及equals()相等的对象,在散列表中只存在一个。
如果重写equals()不重写hashCode():默认会调用Object.java中的本地方法hashCode(),这会导致,在equals()判断相等的两个对象,哈希码不同,从而与我们希望的逻辑结果不同。
举例
创建一个User类,分两次实验
- 重写hashCode()
- 不重写hashCode()
public class User {
private String name;
private int hash;
public User(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
// return super.equals(obj);
// if (obj.getClass().equals(User.class)) {}
if (obj instanceof User) {
User c = (User) obj;
return name == c.name;
}
return false;
}
@Override
public int hashCode(){
return Objects.hash(name);
}
}
执行的主方法类
public class HashMapDemo {
public static void main(String[] args) {
User user1 = new User("李一");
User user2 = new User("李一");
System.out.println("user1.hashCode())>>>"+ user1.hashCode());
System.out.println("user2.hashCode())>>>"+ user2.hashCode());
HashMap<User ,String>map = new HashMap<>();
map.put(user1,"李一");
System.out.println("Key>>user1 "+map.get(user1));
System.out.println("Key>>user2 "+map.get(user2));
}
}
运行结果
第一次:不重写hashCode()
第二次:重写hashCode()
说明:
- 重写的hashCode()中,调用了Objects类中的hash(value),其功能是根据参数通过某种哈希函数产生哈希码,这里是根据name产生哈希码
分析
可以发现,两次执行的结果中,前者未重写hashCode(),对象相等,但是哈希值不相等,导致在HashMap中相当于两个不同的key,无法通过相等对象获取value。而重写后hashCode()则实现了期望。
总结
- equals()与操作符==的关系
简单的说- equals()经过重写后要达到判断值相等的功能
- 操作符==:是判断引用类型地址的是否相等以及基础类型(byte,int,short,long,char,double,float,char,boolean)的值是否相等的
- equals()与hashCode的关系
- hashCode()返回整数,及哈希值,它是定义在Object.java中的
- 重写equals()必要重写hashCode(),因为在类被用于散列表中时,不重写hashCode()会导致equals()相等的对象在散列表中被认为是不同的。
我要在最后重复一遍的是:有人可能会认为只要不将该类用在散列表类型中就不需要在重写equals()情况下重写hashCode(),当然从执行上来说是这样没错,但是为了代码的可扩展性安全性来讲,是有问题的,因为你无法预料到未来是否会将其用到散列表中。所以在日常编程中,严格遵守设计规范是有必要的。