因为某些原因要重新找工作,这次打算系统的复习一下,找了一些BAT等大公司的面试题最全的BAT大厂面试题整理,这里记录一下。
另外就是,当开始复习的时候,我才发现,一个简单的问题里面涉及到的知识量可不少。
最简单的如标题所示的这个,看了这篇文章你会发现,原来并不是那么简单?如果面试官真的问到这个问题,我们更加深入的回答是不是更好呢。
1. 简单的概念性说法。
==是运算符,比较两个变量的值是否相同,八大基本数据类型(byte short int long float double char boolean)比较的是他们的值是相等。其他对象则是比较他们的地址值是否相等。
equals是比较两个对象是否相同,它在Object中的默认实现就是==。
public boolean equals(Object obj) {
return (this == obj);
}
hashCode(散列码)则是对象生成的一个散列码,它也是Object中的方法,对象的散列码是为了更好的支持基于哈希机制的Java集合类,例如 Hashtable, HashMap, HashSet 等。
2. 注意:覆盖equals方法时,也必须覆盖hashCode方法
以下内容摘自《Effective java》 更详细的问题请自行查阅。
Object规范[JavaSE6]约定内容:
如果两个对象根据equals方法比较时相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的正数结果。
如果不这样做,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,比如HashMap, HashSet, HashTable。
例如:
public final class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(short areaCode, short prefix, short lineNumber) {
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNumber = lineNumber;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
PhoneNumber that = (PhoneNumber) o;
return areaCode == that.areaCode &&
prefix == that.prefix &&
lineNumber == that.lineNumber;
}
// Broken - no hashCode method!!!
}
假设你其余将这个类与HashMap一起使用:
HashMap<PhoneNumber, String> m = new HashMap<>();
m.put(new PhoneNumber(707, 867, 5039), "Jenny");
这个时候,你可能期望m.get(new PhoneNumber(707, 867, 5039);
会返回Jenny,但它实际上返回的是null。注意,这里设计两个PhoneNumber实例:第一个被用于插入到HashMap中,第二个实例实际上与第一个相等,被用于获取。
返回null是因为HashMap是以key的hashCode作为索引的,而两个PhoneNumber有两个不同的hashCode,所以无法找到对象。
解决方式就是让equls相同的两个PhoneNumber对象也返回相同的hashCode。需要注意的是,hashCode使用到的域要和equals的保持一致,确保符合Object规范[JavaSE6]约定。
3. 计算hashCode的方式
而计算hash值有个通用的公式可以使用 result = 31 * result + c
,直接相加可能导致不同参数产生相同的结果(1+2 == 2+1),所以需要先乘以一个数后再相加,如下
@Override
public int hashCode() {
int result = 1;
result = 31 * result + areaCode
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}
但是如果areaCode和prefix等这些参数不是int类型的时候,可能无法这么方便的使用,需要转换成int。
例如,如果areaCode是一个long类型,则需要这么转换(int)(f ^ (f>>32))
实际上Java就提供了计算HashCode的方法给我们:
@Override
public int hashCode() {
return Objects.hash(areaCode, prefix, lineNumber);
}
源码如下:
Objects.java
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
Arrays.java
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
可以看到源码也是使用 result = 31 * result + c
这个公式。
那么31这个数字是怎么来的呢,为什么要使用它。
关于这点,主要有两个方面的原因:
1. 31是一个质数,习惯上都使用质素来计算散列结果。
2. 31可以用位移和减法来代替乘法,得到更好的性能:31 * i = (i << 5) - i
。JVM会自动完成这种优化。
第一点涉及到hash算法的数学问题,这里就不多研究了,非要钻牛角尖的话可以自行搜索资料。