🔍 一、Collections.newSetFromMap()
📋 API概览
java
public static <E> Set<E> newSetFromMap(Map<E, Boolean> map)
核心作用: 将任意 Map
转换为具有相同特性的 Set
实现
🎯 设计目的
💡 核心思想
这个API的设计基于一个重要观察:Set本质上就是只关心key的Map
java
🎭 Set ≈ Map<E, 无意义的值>
├── Set存储元素 → Map的Key
├── Set判断存在 → Map的containsKey()
└── Set遍历元素 → Map的keySet()
🚀 解决的问题
在JDK中,并不是每种Map都有对应的Set实现:
Map类型 | 是否有对应Set | 说明 |
---|---|---|
HashMap |
✅ HashSet |
有对应实现 |
TreeMap |
✅ TreeSet |
有对应实现 |
LinkedHashMap |
✅ LinkedHashSet |
有对应实现 |
WeakHashMap |
❌ | 缺少对应Set |
ConcurrentHashMap |
❌ | 缺少对应Set |
问题: 如何创建具有特定Map特性的Set?
🛠️ 工作原理
🔧 实现机制
java
// 内部实现概念图
class SetFromMap<E> implements Set<E> {
private final Map<E, Boolean> map;
private transient Set<E> keySet;
SetFromMap(Map<E, Boolean> map) {
this.map = map;
this.keySet = map.keySet();
}
// Set的所有操作都委托给Map
public boolean add(E e) {
return map.put(e, Boolean.TRUE) == null;
}
public boolean contains(Object o) {
return map.containsKey(o);
}
// ... 其他方法类似
}
📊 操作映射关系
Set操作 | 对应Map操作 | 说明 |
---|---|---|
add(e) |
put(e, Boolean.TRUE) |
添加元素 |
remove(e) |
remove(e) |
删除元素 |
contains(e) |
containsKey(e) |
判断存在 |
size() |
size() |
获取大小 |
iterator() |
keySet().iterator() |
遍历元素 |
addAll(collection) |
多次put() 调用 |
批量添加 |
🎪 典型使用场景
1️⃣ 弱引用Set - WeakHashSet
java
// 创建一个弱引用的Set,元素可能被GC回收
Set<Object> weakHashSet = Collections.newSetFromMap(
new WeakHashMap<Object, Boolean>()
);
// 使用场景:缓存、临时对象集合
weakHashSet.add(someObject);
// 当someObject没有其他强引用时,可能被自动清理
2️⃣ 线程安全Set - ConcurrentHashSet
java
// 创建一个线程安全的Set
Set<String> concurrentSet = Collections.newSetFromMap(
new ConcurrentHashMap<String, Boolean>()
);
// 使用场景:多线程环境下的共享集合
concurrentSet.add("thread-safe-element");
⚠️ 重要约束条件
🚨 使用限制
1. Map必须为空
java
Map<String, Boolean> map = new HashMap<>();
map.put("existing", Boolean.TRUE);
// ❌ 这会抛出IllegalArgumentException
Set<String> set = Collections.newSetFromMap(map);
2. 不能直接访问原Map
java
Map<String, Boolean> backingMap = new HashMap<>();
Set<String> set = Collections.newSetFromMap(backingMap);
// ❌ 危险操作:直接操作backing map可能导致不一致
backingMap.put("direct", Boolean.FALSE); // 值不是Boolean.TRUE!
3. 正确的使用模式
java
// ✅ 推荐:一次性创建,不保留Map引用
Set<Object> properSet = Collections.newSetFromMap(
new WeakHashMap<Object, Boolean>()
);
// 没有保留WeakHashMap的引用,安全!
🔍 二、Collections.newSequencedSetFromMap
🆕 SequencedSet的核心价值
🎯 解决的痛点
在Java 21之前,处理有序集合时存在API不一致的问题:
java
// 😞 Java 21之前的困扰
LinkedHashSet<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("first");
linkedSet.add("second");
linkedSet.add("third");
// ❌ 无法直接获取第一个/最后一个元素
// String first = linkedSet.getFirst(); // 不存在此方法
// String last = linkedSet.getLast(); // 不存在此方法
// 😭 只能通过Iterator间接获取
String first = linkedSet.iterator().next(); // 获取第一个
// 获取最后一个更麻烦...需要遍历到最后
🚀 SequencedSet解决方案
java
// 😊 Java 21的优雅解决方案
SequencedSet<String> sequencedSet = new LinkedHashSet<>();
sequencedSet.addFirst("first");
sequencedSet.addLast("last");
// ✅ 直接、优雅的API
String first = sequencedSet.getFirst(); // "first"
String last = sequencedSet.getLast(); // "last"
String removed = sequencedSet.removeFirst(); // 移除并返回第一个
// ✅ 反向视图
SequencedSet<String> reversed = sequencedSet.reversed();
🔥 newSequencedSetFromMap的独特优势
💎 核心亮点
1. 突破LinkedHashSet的限制
特性 | LinkedHashSet | newSequencedSetFromMap + LinkedHashMap |
---|---|---|
基础有序Set功能 | ✅ 支持 | ✅ 支持 |
SequencedSet接口 | ✅ 实现了 | ✅ 实现了 |
自定义淘汰策略 | ❌ 不支持 | 🌟 支持! |
重写访问顺序 | ❌ 不支持 | 🌟 支持! |
2. LRU Set的完美实现
java
// 🚀 创建一个真正的LRU Set!
SequencedSet<String> lruSet = Collections.newSequencedSetFromMap(
new LinkedHashMap<String, Boolean>(16, 0.75f, true) { // accessOrder=true
// 在访问序模式下,这个方法在 put 之后被调用,返回 true 则移除最老的条目,false 则不移除
@Override
protected boolean removeEldestEntry(Map.Entry<String, Boolean> eldest) {
return size() > 4; // 最多4个元素
}
}
);
lruSet.add("A");
lruSet.add("B");
lruSet.add("C");
System.out.println(lruSet); // [A, B, C]
lruSet.addAll(Arrays.asList("A", "D"));
// add 与 addAll 触发重排序: A 从链表头部移动到链表末尾,然后将 D 添加到链表末尾
System.out.println(lruSet); // [B, C, A, D]
lruSet.add("E");
// 元素 A 被删除,因为已满
System.out.println(lruSet); // [C, A, D, E]
这是LinkedHashSet永远无法实现的功能! 🎯
补充两点
- 下面两段代码可以认为是等价的,唯一区别是对外暴露的API风格(Map或Set)不同
javaLinkedHashMap<String, Boolean> lruMap = new LinkedHashMap<>(16, 0.75f, true) { // accessOrder=true @Override protected boolean removeEldestEntry(Map.Entry<String, Boolean> eldest) { return size() > 4; // 最多4个元素 } }; SequencedSet<String> lruSet = Collections.newSequencedSetFromMap( new LinkedHashMap<String, Boolean>(16, 0.75f, true) { // accessOrder=true @Override protected boolean removeEldestEntry(Map.Entry<String, Boolean> eldest) { return size() > 4; // 最多4个元素 } } );
- LinkedHashMap有两种排序模式:插入序 和访问序 。在访问序模式下,最近最少访问的条目(least-recently accessed)排在最前面(链表头部),而最近最多访问的条目(most-recently accessed)排在最后(链表末尾)。 在访问序模式下,会触发元素重排序的操作有:
put
、putIfAbsent
、get
、getOrDefault
、compute
、computeIfAbsent
、computeIfPresent
、merge
、replace
等;不会触发元素重排序的操作有:containsKey
、remove
,keySet
、values
、entrySet
、Sequenced
等相关视图操作。
🌟 总结
Collections.newSetFromMap()
的真正价值在于:
- 🔧 填补空白 - 为没有对应Set实现的Map提供Set视图
- 🎯 特性继承 - 完美继承底层Map的所有特性
- 🚀 扩展性 - 支持任何Map实现,包括自定义Map
- 💡 设计优雅 - 通过适配器模式实现接口转换
newSequencedSetFromMap
不是 newSetFromMap
的替代品,而是其在有序集合领域的专业化增强!