Map 与 hashCode/equals
约 810 字大约 3 分钟
布欧-Lewyon
2026-05-15
首页 › Java › 集合 › Map 与 hashCode/equals
Map 存储键值对(key-value),每个键映射到一个值。键不可重复,值可重复。理解 hashCode 与 equals 协定是正确使用 HashMap 的前提。
Map 接口
HashMap
HashMap 基于哈希表实现,键无序,查找 O(1):
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Charlie", 92);
int aliceScore = scores.get("Alice"); // 95,键不存在时返回 null
scores.getOrDefault("David", 0); // 0,不存在时返回默认值
scores.containsKey("Bob"); // true遍历 Map 有三种方式:
// 遍历键值对(最常用)
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
// 只遍历键
for (String name : scores.keySet()) { ... }
// 只遍历值
for (int score : scores.values()) { ... }
// 用 forEach + Lambda(Java 8+)
scores.forEach((name, score) -> System.out.println(name + " = " + score));TreeMap
TreeMap 基于红黑树,键按自然顺序或 Comparator 排序,操作 O(log n):
Map<String, Integer> sorted = new TreeMap<>();
sorted.put("Charlie", 92);
sorted.put("Alice", 95);
sorted.put("Bob", 87);
System.out.println(sorted); // {Alice=95, Bob=87, Charlie=92}(键字母序)LinkedHashMap
LinkedHashMap 保留插入顺序或访问顺序(后者可实现 LRU 缓存):
Map<String, Integer> linked = new LinkedHashMap<>(16, 0.75f, true); // access-order
linked.put("A", 1);
linked.put("B", 2);
linked.get("A"); // 访问 A,A 移到末尾equals 与 hashCode 协定
HashMap 判断键是否重复时,先比较 hashCode(定位到"桶"),再比较 equals(桶内精确匹配)。自定义对象作为 HashMap 的键时,必须重写这两个方法。
协定
- 若
a.equals(b)为true,则a.hashCode() == b.hashCode()必须成立。 - 若
a.hashCode() == b.hashCode(),a.equals(b)可返回true或false(哈希碰撞)。 - 多次调用同一对象的
hashCode(),应返回相同值(若参与计算的字段未改变)。
一个完整的示例
public class Student {
private final String id; // 学号,不可变
private final String name;
public Student(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student s = (Student) o;
return id.equals(s.id); // 以学号作为唯一标识
}
@Override
public int hashCode() {
return id.hashCode(); // 与 equals 使用相同的字段
}
}Map<Student, Integer> map = new HashMap<>();
map.put(new Student("S001", "Alice"), 95);
map.put(new Student("S001", "Alice"), 98); // 键相同,覆盖值为 98
System.out.println(map.size()); // 1常见错误
- 只重写
equals不重写hashCode:导致HashMap中两个"相等"的对象落在不同桶,查不到。 - 用可变字段计算
hashCode:对象存入 HashMap 后修改了参与hashCode的字段,导致get/remove查不到(定位到了错误的桶)。 - 继承关系中
equals不对称:父类与子类的equals用getClass()而非instanceof检查可避免不对称问题。
小结
Map<K,V>存储键值对:HashMap(最快,无序)、TreeMap(键排序)、LinkedHashMap(保留顺序)。- 自定义对象做 Map 键时必须同时重写
equals和hashCode,且使用相同的字段集合。 hashCode负责定位桶,equals负责桶内精匹配;equals为true时hashCode必须相等。- 易错:对象存入
HashMap后不要修改参与hashCode的字段,否则get会返回null。Map.get在键不存在时返回null,不要和"键存在但值为 null"混淆——用containsKey区分。 - 思考任务:定义一个
Book类(isbn、title),重写equals和hashCode(基于isbn);将其作为HashMap<Book, Double>的键,验证 ISBN 相同即视为同一本书。
上一节:List 与 Set
下一节:迭代与排序
