一、HashMap中存储自定义对象
如果要在使用到Hash的集合(HashSet、HashMap...)上用到我们自己创建的实体类,则需要重写hashCode()和queals()方法。否则Hash集合将无法正确区分我们自己创建的实体类对应的对象。
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<Person, Profession> map = new HashMap<>();
map.put(new Person("ZanSan"), new Profession("Teacher"));
map.put(new Person("LiSi"), new Profession("Scientist"));
Person wangWuInMap = new Person("WangWu");
System.out.printf("WangWuInMao, hashCode: %d \n", wangWuInMap.hashCode());
map.put(wangWuInMap, new Profession("Java Developer"));
System.out.println("map entrys: ");
map.entrySet()
.stream()
.peek(System.out::println)
.count(); //调用终结操作 stream才会真正执行
System.out.println("find WangWu: ");
Person findWangWu = new Person("WangWu");
System.out.printf("findWangWu, hashCode: %d \n", findWangWu.hashCode());
Profession wangWu = map.get(findWangWu);
System.out.println("find Result: " + wangWu);
}
}
/**
* Output:
* WangWuInMao, hashCode: 1956725890
* map entrys:
* Person{name='WangWu'}=Profession{name='Java Developer'}
* Person{name='LiSi'}=Profession{name='Scientist'}
* Person{name='ZanSan'}=Profession{name='Teacher'}
* find WangWu:
* findWangWu, hashCode: 1531448569
* find Result: null
*
* Process finished with exit code 0
*/
显然,WangWuInMao和findWangWu的hashCode并不一致,且他们的内存地址不用,使用equals()对比他们是否相等也是会返回false。所有我们要在Person中重写hashCode()和queals()方法。
二、重写hashCode()和queals()
在Effective Java Programming Language Guide一书中,Joshua Bloch给出了一个生成比较好的hashCode()基本准则。
1.把一个非零常数,比如17,保存在一个叫result的变量中。
2.对于对象中的每个重要字段f(即equals()方法会考虑的每个字段),计算出该字段的int类型的哈希码c,具体如表 C-1所示。
3.组合上面计算出来的哈希码:result = 37 * result + C;。
4.返回 result 值。
5.观察得到的 hashCode(),确保相等的实例具有相同的哈希码。
一个适当的equals()方法必须满足以下五个条件。
1.自反性:对于任意的x,调用 x.equals(x)时应该返回true。
2.对称性:对于任意的x和y,当且仅当y.equals(x)返回true时,x.equals(y)返回true。
3.传递性:对于任意的x、y和z,如果x.equals(y) 和 y.equals(z)都返回true,那么x.equals(z) 也应该返回true。
4.一致性:对于任意的x和y,如果对象中用于相等性比较的信息没有被修改过,那么多次调用 x.equals(y)应该始终返回true或false。
5.对于任意非空的x,调用x.equals(null)应该返回 false。
下面是满足这些条件的一系列测试,它可以确定自身与所比较的对象(在这里称为rval)是否相等。
1.如果rval为null,则两个对象不相等。
2.如果rval为this对象(即自己和自己比较),则两个对象相等。
3.如果rval不是this 对象所属的类或子类,则两个对象不相等。
4.如果以上测试都通过,接下来就需要确定rval 中哪些字段是重要的(并且是一致的),然后对它们进行比较。
根据上面的hashCode()和equals()方法的准则,我们为Person生成对应的hashCode()和equals()方法,为了更好的展示,给Person对象添加的id字段。
import java.util.Objects;
public class Person {
private int id;
private String name;
public Person(Integer id, String name) {
this.id = id;
this.name = name;
}
@Override
public int hashCode() {
//最简单的实现
//return name.hashCode() * id;
int result = 17;
result = 37 * result + id;
result = 37 * result + name.hashCode();
return result;
}
@Override
public boolean equals(Object rval) {
return rval instanceof Person
&& Objects.equals(id, ((Person) rval).id)
&& Objects.equals(name, ((Person) rval).name);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
import java.util.HashMap;
public class Main {
public static void main(String[] args) {
Map<Person, Profession> map = new HashMap<>();
map.put(new Person(1, "ZanSan"), new Profession("Teacher"));
map.put(new Person(2, "LiSi"), new Profession("Scientist"));
Person wangWuInMap = new Person(3, "WangWu");
System.out.printf("WangWuInMao, hashCode: %d \n", wangWuInMap.hashCode());
map.put(wangWuInMap, new Profession("Java Developer"));
System.out.println("map entrys: ");
map.entrySet()
.stream()
.peek(System.out::println)
.count(); //调用终结操作 stream才会真正执行
System.out.println("find WangWu: ");
Person findWangWu = new Person(3, "WangWu");
System.out.printf("findWangWu, hashCode: %d \n", findWangWu.hashCode());
Profession wangWu = map.get(findWangWu);
System.out.println("find Result: " + wangWu);
}
}
/**
* Output:
* WangWuInMao, hashCode: -1711247431
* map entrys:
* Person{name='LiSi'}=Profession{name='Scientist'}
* Person{name='ZanSan'}=Profession{name='Teacher'}
* Person{name='WangWu'}=Profession{name='Java Developer'}
* find WangWu:
* findWangWu, hashCode: -1711247431
* find Result: Profession{name='Java Developer'}
*/
经过修改后,能正确的从map中找到内容完全相同的Person对象。
Person对象中equals()方法没有与equals()方法准则对应的代码是因为:
1.使用了instanceof进行检查后就不必再检查null了。
2.与this比较是多余的,只要能正确编写queals()方法,自我比较就不会有问题。
三、使用Objects提供hashCode生成方法
通常情况下都不需要我们自己手动实现hashCode的生成方法,而是直接使用
Objects.hashCode(Object o) //单个字段
Objects.hash(Object... values) //多个字段
@Override
public int hashCode() {
//最简单的实现
//return name.hashCode() * id;
/*int result = 17;
result = 37 * result + id;
result = 37 * result + name.hashCode();
return result;*/
return Objects.hash(id, name);
}
四、自定义简单的HashMap
Java8中HashMap是通过数组+链表+红黑树实现的,这里只是简单的用数组+链表来实现。并且entrySet()方法只是简单的将数据复制到一个HashSet中,对entrySet进行添加和删除并不能直接映射到HashMap上。
import java.util.*;
public class SimpleHashMap<K, V> extends AbstractMap<K, V> {
//为哈希表的大小分配一个指数,以实现均匀分布
private int capacity = 997;
private LinkedList<MapEntry<K, V>>[] table = new LinkedList[capacity];
@Override
public V put(K key, V value) {
int index = Math.abs(key.hashCode() % 997);
LinkedList<MapEntry<K, V>> mapEntries = table[index];
if (mapEntries == null) {
LinkedList<MapEntry<K, V>> newEntries = new LinkedList<>();
MapEntry<K, V> entry = new MapEntry<>(key, value);
newEntries.add(entry);
table[index] = newEntries;
return value;
}
for (MapEntry<K, V> mapEntry : mapEntries) {
if (Objects.equals(mapEntry.key, key)) {
V oldValue = mapEntry.getValue();
mapEntry.setValue(value);
return oldValue;
}
}
MapEntry<K, V> entry = new MapEntry<>(key, value);
mapEntries.add(entry);
return value;
}
@Override
public V get(Object key) {
int index = Math.abs(key.hashCode() % capacity);
for (MapEntry<K, V> mapEntry : table[index]) {
if (key.equals(mapEntry.key)) {
return mapEntry.value;
}
}
return null;
}
@Override
public Set<Entry<K, V>> entrySet() {
Set<Entry<K, V>> mapEntries = new HashSet<>();
for (LinkedList<MapEntry<K, V>> entries : table) {
if (entries != null && entries.size() > 0)
mapEntries.addAll(entries);
}
return mapEntries;
}
class MapEntry<K, V> implements Map.Entry<K, V> {
private K key;
private V value;
MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
this.value = value;
return value;
}
@Override
public String toString() {
return "MapEntry{" +
"key=" + key +
", value=" + value +
'}';
}
}
}
使用SimpleHashMap替换HashMap
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<Person, Profession> map = new SimpleHashMap<>();
map.put(new Person(1, "ZanSan"), new Profession("Teacher"));
map.put(new Person(2, "LiSi"), new Profession("Scientist"));
Person wangWuInMap = new Person(3, "WangWu");
System.out.printf("WangWuInMao, hashCode: %d \n", wangWuInMap.hashCode());
map.put(wangWuInMap, new Profession("Java Developer"));
System.out.println("map entrys: ");
map.entrySet()
.stream()
.peek(System.out::println)
.count(); //调用终结操作 stream才会真正执行
System.out.println("find WangWu: ");
Person findWangWu = new Person(3, "WangWu");
System.out.printf("findWangWu, hashCode: %d \n", findWangWu.hashCode());
Profession wangWu = map.get(findWangWu);
System.out.println("find Result: " + wangWu);
}
}
/**
* Output:
* WangWuInMao, hashCode: -1711247431
* map entrys:
* Person{name='LiSi'}=Profession{name='Scientist'}
* Person{name='ZanSan'}=Profession{name='Teacher'}
* Person{name='WangWu'}=Profession{name='Java Developer'}
* find WangWu:
* findWangWu, hashCode: -1711247431
* find Result: Profession{name='Java Developer'}
*/