1. 自定义对象必须要重写hashCode()和equals()方法吗
不是。
所有Java对象都默认会继承Object对象,Obejtc对象有hashCode()和equals(),不过默认的hashCode()方法返回的是内存地址(native方法,所以是物理内存地址还是JMM内存地址不是很清楚,并且应该是对内存地址做了处理),默认的equals()比较的是两个对象的内存地址,即是不是同一个对象。
如果默认的方法满足你的要求,甚至你根本就用不到这两个方法,那可以不重写,只不过你要记着下次用到的时候要再来重写。。。。。。这就是规范让你重写的原因。
2. 重写了equals()方法必须重写hashCode()方法吗
不是。
这两个方法应用的最多的场合就是HashMap,我们来看一下HashMap使用Object作为key进行put操作时发生了什么事呢?
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
使用key的hashCode()值的高16位与低16位做异或运算的结果作为哈希值,确定该对象在HashMap的Entry数组中的索引下标。
假设现有TestObject对象,
public class TestObject {
String name;
String description;
public TestObject(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestObject that = (TestObject) o;
return Objects.equals(name, that.name);
}
}
重写了equals()方法,目的是name一样就认为是同一个TestObject对象,不管description属性。我们执行下面的方法:
public static void main(String[] args) {
TestObject one = new TestObject("aaa");
TestObject two = new TestObject("aaa");
HashMap<TestObject, Integer> map = new HashMap<>();
map.put(one, 1);
map.put(two, 2);
System.out.println(one.equals(two));
System.out.println(map.size());
}
输出:
true
2
明明one和two经过equals比较是相等的,但是在HashMap里面还是两个key,因为hashcode是不等的,HashMap先判断key的hashcode,相等进入Entry数组的同一个下标位置,此时才会比较equals,决定是相同key覆盖还是不同key形成链表。
虽然不是必须,但是看到这里想必你还是能得出结论,最好还是重写一下吧。
3. 重写了hashCode()方法必须重写equals()方法吗
这回的答案真的是,不必。
只不过hashcode重写的不好的话,可能造成大量不同对象的hashcode值一样,从而导致加入hashmap时Entry数组大量位置是空的,而某些位置是非常长的链表(为了严谨,这里提一下链表与红黑树的转换,但是不详细解释,可以去搜索,网上解释很多),非常不好。
4. 为什么不要使用数组作为HashMap的key
终于回到题目。
因为数组不是我们自定义对象,并且数组的hashCode()方法返回的是内存地址。假设我们期待HashMap对数组去重或者统计相同数组数量,这时候就会遇到2中的问题,相同的数组却是不同的key。
5. 补充,HashMap的key还有哪些要求?(面试题)
除了上面的几点,还需要注意HashMap的key最好是不可变对象。
仍以上面的TestObject举例,假设你重写了hashCode和equalis方法:
public class TestObject {
String name;
String description;
public TestObject(String name) {
this.name = name;
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TestObject that = (TestObject) o;
return Objects.equals(name, that.name);
}
}
执行下面的代码:
public static void main(String[] args) {
TestObject one = new TestObject("aaa");
HashMap<TestObject, Integer> map = new HashMap<>();
map.put(one, 1);
System.out.println(map.get(one));
one.setName("bbb");
System.out.println(map.get(new TestObject("aaa")));
System.out.println(map.get(new TestObject("bbb")));
System.out.println(map.get(one));
}
输出:
1
null
null
null
TestObject被修改后,hashcode和equals和原来的对象都不同了,之前存储在HashMap中的TestObject对象被改变了,但是在Enry数组中的位置并不会跟着改变。相当于是按照aaa对象的hashcode值找到Entry数组的对应位置却存了一个bbb对象,这时候无论是原来的对象、还是修改的对象、还是新建的aaa对象、新建的bbb对象都无法找到之前存储在HashMap中的1了。
我们最常用的String作为HashMap的key,因为它是一个不可变对象。