面试题目
- ==和equals有什么区别?
- 这两个都适用于哪些场景进行比较?
- 为什么重写了equals方法,还必须重写hashcode方法?
这个问题基本上在各面试题库中都会有出现,也是现实项目中使用比较多的一个比较,面试的时候问的也比较多,下面从源码结合案例进行学习,不光会用,还要学会思考为什么用?
题目解析
==和equals有什么区别?
在Object类中,其equals的方法底层实现就是"=="来实现的,下面查看一下Object对象的源码:
public boolean equals(Object obj) {return (this == obj);
}
也就是说,对于 Object 对象来说,equals 和 == 都是一样的,都是比较对象的引用是否相同。
但是,在JDK中的其他类中通常会重写 equals 以实现具体的值比较,例如 Integer、String、Double等都重写了equals。比如String的equals被重写后,比较的是字符值。
如下源码所示。 Integer 中的 equals 实现源码如下:
public boolean equals(Object obj) {if (obj instanceof Integer) {return value == ((Integer)obj).intValue();}return false;}
从上述源码可以看出,Integer 中会先将 Integer 对象转换成基础类型int值来进行比较,所以此时就不再是对比两个对象的引用了,而是对比两个对象的值是否相等。
再来看一下String中的 equals 实现源码如下:
public boolean equals(Object anObject) {// this代表当前类(String)调用equals方法的对象。//this对象将与传入的参数对象anObject比较内存值是否一样,如果一样,返回trueif (this == anObject) {return true;}//使用instanceof关键字 判断传入的参数的类型是不是String类型的实例。if (anObject instanceof String) {//如果判断出传入的参数的类型是String类型,把传入的参数强转为String类型String anotherString = (String)anObject;//value是类中字符数组的名字,通过value.length获得字符数组的长度//String的底层是由char类型的数组实现的。int n = value.length;//比较类中字符数组的长度与传入的参数对象中的字符数组长度是否一样if (n == anotherString.value.length) {//如果长度一样//使用v1[]存储value的字串char v1[] = value;//使用v2[]存储传入的参数中value的字串char v2[] = anotherString.value;//使用循环比较v1[] 和 v2[] 对应位置的字符是否相同。int i = 0;while (n-- != 0) {if (v1[i] != v2[i])return false;i++;}return true;}}//如果两个字符数组的长度不一致,返回falsereturn false;
}
从 String 中的 equals 中可以看出,它和 Integer 一样,是将 Object 中的引用比较重写成了值比较了。
适用于哪些场景进行比较
所以,对于 Object 来说,== 和 equals 都是一样的,都是用来对比两个对象的引用是否相同的,而其他 Java 中的类中,如 String 或 Integer 等,通常都会重写 equals 让其变为比较具体的值是否相同,而非引用是否相同。 所以,我们通常会使用 == 来对比两个对象的引用是否相同,而使用 equals 对比两个值是否相同(前提条件是重写了 equals 方法)。
另外重写了equlas后,也必须重写hashcode()方法
为什么重写了equals方法,还必须重写hashcode方法
- 为了提高效率
采取重写hashcode方法,先进行hashcode比较,如果不同,那么就没必要在进行equals的比较了,这样就大大减少了equals比较的次数,这对比需要比较的数量很大的效率提高是很明显的,一个很好的例子就是在集合中的使用。
例如:Java中的List集合是有序的,因此是可以重复的。
而set集合是无序的,因此是不能重复的,那么怎么能保证不能被放入重复的元素呢,但靠equals方法一样比较的话,如果原来集合中有10000个元素,那么在放入第10001个元素的时候,难道要将前面的所有元素都进行比较,看看是否有重复,这个效率可想而知,因此hashcode就应遇而生了,java就采用了hash表,利用哈希算法(也叫散列算法),就是将对象数据根据该对象的特征使用特定的算法将其定义到一个地址上,那么在后面定义进来的数据只要看对应的hashcode地址上是否有值,那么就用equals比较,如果没有则直接插入,只要就大大减少了equals的使用次数,执行效率就大大提高了。
- 为了保证同一个对象
保证在equals相同的情况下hashcode值必定相同,如果重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(因为equal都是根据对象的特征进行重写的),但hashcode确实不相同的。
hash类存储结构(HashSet、HashMap等等)添加元素会有重复性校验,校验的方式就是先取hashCode判断是否相等(找到对应的位置,该位置可能存在多个元素),然后再取equals方法比较(极大缩小比较范围,高效判断),最终判定该存储结构中是否有重复元素。
重写后equals和hashcode两个方法的关系
- equals()相等的两个对象,hashcode()一定相等。
- hashcode()不等,一定能推出equals()也不等。
- hashcode()相等,equals()可能相等,也可能不等。
所以先进行hashcode()判断,不等就不用equals()方法了。
案例说明
现在来定义一个类,然后想和 Integer 或 String 中的 equals 一样,用其对比值而非引用是否相同的实现代码如下:
@Data
public class Book {private String name;private int num;@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (obj == null || getClass() != obj.getClass()) {return false;}Book book = (Book) obj;return this.name.equals(book.name) && this.num == book.num;}/*** 根据重写的 equals() 方法中用于比较的字段(num 和 name)来计算对象的哈希码。* 通过使用质数 31 这样的系数乘积,可以更好地分散哈希码,减少哈希冲突的可能性。* @return*/@Overridepublic int hashCode() {int result = 17;result = 31 * result + num;result = 31 * result + (name != null ? name.hashCode() : 0);return result;}
}
总结
对于 Object 来说,equals 是用 == 实现的,所以二者是相同的,都是用来比较两个对象的引用是否相同的,但 Java 中的其他类,都会重写 equals 让其变为值比较,而非引用比较,如 Integer 和 String 都是这样。