讲解

Map 存的是「键值对」(key-value):一个键对应一个值,键唯一、值可重复,通过键能在 O(1) 级别直接取到值,不用遍历。最常用实现 HashMap:put(k, v) 存入或覆盖、get(k) 取值(键不存在返回 null)、getOrDefault(k, 默认值) 带兜底取值、containsKey 判断键、remove 删除、size 大小。遍历 Map 的标准写法是 for (Map.Entry<K, V> e : map.entrySet()),同时拿到键和值;只关心键用 keySet(),只关心值用 values()。HashMap 不保证遍历顺序,需要按插入顺序用 LinkedHashMap,需要按键排序用 TreeMap。

Set 是「不重复的集合」:只存元素不存值,重复的元素加不进去(add 返回 false 表示已存在)。最常用 HashSet,典型用途是去重和快速判存:把名单丢进 HashSet,重复自动消失;contains 判断某人是否在集合里也是 O(1)。Set 不支持下标访问(元素没有位置概念),遍历用增强 for。需要有序去重用 TreeSet(自动排序)或 LinkedHashSet(保持插入序)。

Map 和 Set 的「唯一性」判断都依赖元素的 equals 和 hashCode 方法——自定义类做键或 Set 元素时,必须正确重写这两个方法,否则「明明一样的两个对象」会被当成不同键存两份。这是后面常用类一章的核心内容。另外统计词频这类场景有个简洁组合拳:map.merge(word, 1, Integer::sum),键不存在就存 1,存在就把旧值加 1。

示例

HashMap 存取与遍历,getOrDefault 兜底:

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("李明", 88);
        scores.put("王芳", 92);
        scores.put("张伟", 75);
        scores.put("李明", 90);

        System.out.println("李明的成绩:" + scores.get("李明"));
        System.out.println("赵磊的成绩:" + scores.getOrDefault("赵磊", 0));
        System.out.println("包含王芳?" + scores.containsKey("王芳"));
        System.out.println("人数:" + scores.size());

        for (Map.Entry<String, Integer> e : scores.entrySet()) {
            System.out.println(e.getKey() + " => " + e.getValue());
        }
    }
}

HashSet 去重与快速判存,merge 统计词频:

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set<String> attendees = new HashSet<>();
        attendees.add("李明");
        attendees.add("王芳");
        attendees.add("李明");
        System.out.println("实到人数:" + attendees.size());
        System.out.println("王芳到了吗?" + attendees.contains("王芳"));
        System.out.println("名单:" + attendees);

        String[] words = {"java", "python", "java", "go", "java", "python"};
        Map<String, Integer> counter = new HashMap<>();
        for (String w : words) {
            counter.merge(w, 1, Integer::sum);
        }
        System.out.println("词频统计:" + counter);
    }
}

常见坑

  • get 返回 null 直接拆箱:int score = map.get("不存在"); 对 null 自动拆箱直接抛 NullPointerException。用 getOrDefault 或先 containsKey 判断。
  • 以为 HashMap 遍历有序:HashMap 的顺序是哈希结果,插入顺序和遍历顺序没有必然关系。要顺序选 LinkedHashMap 或 TreeMap。
  • 自定义对象做键却不重写 equals/hashCode:两个「内容相同」的对象被当成不同的键,get 永远拿不到 put 进去的值。规则见常用类一章。
  • 在遍历 Map 时直接 put/remove:和 List 一样会抛并发修改异常。先收集要删的键,遍历结束后再统一操作。

小结

HashMap 按键值存取、getOrDefault 兜底、entrySet 遍历;HashSet 去重判存;唯一性都靠 equals/hashCode。下一节理解集合声明里那对尖括号:泛型。