14_Java 泛型与集合框架
List、Set、Map 的选择,equals/hashCode 契约及泛型通配符。
Java 泛型与集合框架
学习目标:根据语义选择 List、Set、Map,理解泛型约束与对象相等规则。
1. 三类常用集合
| 类型 | 适合场景 | 常见实现 |
|---|---|---|
List |
有顺序、可重复 | ArrayList |
Set |
元素唯一 | HashSet、LinkedHashSet |
Map |
键到值的查找 | HashMap、LinkedHashMap |
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Main {
public static void main(String[] args) {
List<String> names = List.of("甲", "乙");
Map<String, Integer> counts = new HashMap<>();
for (String name : names) counts.merge(name, 1, Integer::sum);
System.out.println(counts.getOrDefault("甲", 0));
}
}
List.of 返回不可修改的集合;不能 add,也不接受 null。HashMap 不保证遍历顺序,若顺序有业务含义,明确选择 LinkedHashMap 或排序后的视图。
2. equals 与 hashCode
相等对象必须有相同哈希值。若自定义对象作为 HashSet 元素或 HashMap 键,需要一致地实现 equals 与 hashCode。放入后不要修改参与哈希计算的字段,否则可能再也找不到该键。不可变 record 常适合值对象。
3. 泛型与通配符
List<String> 不是 List<Object> 的子类型;否则有人可向字符串列表写入整数。通配符处理方向不同:
List<? extends Number>:适合从列表读取Number;通常不能向其添加新数字。List<? super Integer>:适合写入Integer;读取时只能安全地看作Object。
可用“生产者用 extends,消费者用 super”辅助记忆,但以实际方法签名为准。泛型主要在编译期保证类型安全;运行时的类型擦除限制了 new T() 和直接判断 instanceof List<String> 等写法。
自测
- 需要按插入顺序输出去重元素,应选什么?
LinkedHashSet。 - 为什么重写
equals时通常必须重写hashCode?散列表依赖两者一致性。
4. 选集合还要看操作成本
ArrayList 适合按索引访问和尾部追加;中间插入/删除可能移动大量元素。HashSet 与 HashMap 适合按哈希查找,但迭代顺序未定义;LinkedHashSet、LinkedHashMap 保留插入顺序,TreeSet、TreeMap 按比较器排序。不要把平均复杂度当作绝对保证,元素数量、哈希质量和内存开销也重要。
import java.util.HashMap;
import java.util.Map;
class CountWords {
static Map<String, Integer> count(Iterable<String> words) {
Map<String, Integer> result = new HashMap<>();
for (String word : words) result.merge(word, 1, Integer::sum);
return result;
}
}
修改作为 map 键的对象会破坏查找预期,因为插入时和查找时的哈希值可能不同。值对象最好不可变;若使用 record,确认其组成字段本身的相等语义符合业务需求。
5. 泛型的编译期能力与限制
泛型保证 List<String> 取出的是 String,但运行时类型擦除使 List<String> 与 List<Integer> 的原始类相同。不能直接 new T(),也不能写 instanceof List<String>。通配符适用于 API 边界:只读入数字用 List<? extends Number>;向容器写入整数用 List<? super Integer>。
练习:实现从 List<? extends Number> 求和的方法,再实现向 List<? super Integer> 写入 1 到 3 的方法,解释读取结果为何只能安全看成 Object。