newSetFromMap() & newSequencedSetFromMap() 笔记

🔍 一、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永远无法实现的功能! 🎯

补充两点

  1. 下面两段代码可以认为是等价的,唯一区别是对外暴露的API风格(Map或Set)不同
java 复制代码
LinkedHashMap<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个元素
       }
    }
);
  1. LinkedHashMap有两种排序模式:插入序访问序 。在访问序模式下,最近最少访问的条目(least-recently accessed)排在最前面(链表头部),而最近最多访问的条目(most-recently accessed)排在最后(链表末尾)。 在访问序模式下,会触发元素重排序的操作有:putputIfAbsentgetgetOrDefaultcomputecomputeIfAbsentcomputeIfPresentmergereplace等;不会触发元素重排序的操作有:containsKeyremovekeySetvaluesentrySetSequenced等相关视图操作。

🌟 总结

Collections.newSetFromMap()的真正价值在于:

  1. 🔧 填补空白 - 为没有对应Set实现的Map提供Set视图
  2. 🎯 特性继承 - 完美继承底层Map的所有特性
  3. 🚀 扩展性 - 支持任何Map实现,包括自定义Map
  4. 💡 设计优雅 - 通过适配器模式实现接口转换

newSequencedSetFromMap 不是 newSetFromMap 的替代品,而是其在有序集合领域的专业化增强!

相关推荐
Mr Aokey1 小时前
Spring MVC参数绑定终极手册:单&多参/对象/集合/JSON/文件上传精讲
java·后端·spring
地藏Kelvin1 小时前
Spring Ai 从Demo到搭建套壳项目(二)实现deepseek+MCP client让高德生成昆明游玩4天攻略
人工智能·spring boot·后端
菠萝012 小时前
共识算法Raft系列(1)——什么是Raft?
c++·后端·算法·区块链·共识算法
长勺2 小时前
Spring中@Primary注解的作用与使用
java·后端·spring
小奏技术3 小时前
基于 Spring AI 和 MCP:用自然语言查询 RocketMQ 消息
后端·aigc·mcp
编程轨迹3 小时前
面试官:如何在 Java 中读取和解析 JSON 文件
后端
lanfufu3 小时前
记一次诡异的线上异常赋值排查:代码没错,结果不对
java·jvm·后端
编程轨迹3 小时前
如何在 Java 中实现 PDF 与 TIFF 格式互转
后端
编程轨迹3 小时前
面试官:你知道如何在 Java 中创建对话框吗
后端
编程轨迹3 小时前
深入理解 Java 中的信号机制
后端