真正受限访问需通过自定义Iterator控制每次遍历,而非暴露原始集合或不可变包装;其核心是在hasNext()和next()中嵌入权限检查、限流、过滤等逻辑,并覆盖remove()和forEachRemaining()以杜绝绕过。

为什么不能直接暴露 private List 字段
因为返回 new ArrayList(privateList) 或 Collections.unmodifiableList() 只能防写,不能防“看到全部”——调用方仍能通过 size()、get(0)、toArray() 等方式窥探全部元素,违背“受限访问”的本意。
真正受限,是让使用者只能按你设计的顺序、条件、次数去取数据,且无法绕过控制逻辑。迭代器模式的核心价值不在封装,而在“可控遍历”。
- 暴露原始集合或不可变包装,等于交出索引权和长度信息
- 返回自定义
Iterator才能拦截每次next(),插入权限检查、懒加载、限流或转换逻辑 - Java 的
Iterable接口只需实现iterator()方法,不强制暴露底层结构
如何写一个带访问控制的 Iterator 实现
关键不是重写整个迭代器,而是把校验和过滤逻辑下沉到 hasNext() 和 next() 里。比如只允许读前 5 条、或仅返回状态为 ACTIVE 的对象。
示例场景:一个管理用户会话的私有 Map<string session></string>,外部只能按创建时间顺序逐个查看未过期的会话,且每次最多取 10 个:
public class SessionIterable implements Iterable<Session> {
private final Map<String, Session> sessions;
public SessionIterable(Map<String, Session> sessions) {
this.sessions = sessions;
}
@Override
public Iterator<Session> iterator() {
return new SessionIterator();
}
private class SessionIterator implements Iterator<Session> {
private final Iterator<Session> baseIter;
private int count = 0;
SessionIterator() {
// 按创建时间排序后过滤过期项,再转成迭代器
List<Session> validSorted = sessions.values().stream()
.filter(s -> !s.isExpired())
.sorted(Comparator.comparing(Session::getCreatedAt))
.collect(Collectors.toList());
this.baseIter = validSorted.iterator();
}
@Override
public boolean hasNext() {
return count < 10 && baseIter.hasNext();
}
@Override
public Session next() {
if (!hasNext()) throw new NoSuchElementException();
count++;
return baseIter.next();
}
}
}
- 不要在构造
SessionIterator时做耗时操作(如 DB 查询),应提前准备好可迭代源 -
hasNext()必须幂等;next()要负责推进状态,否则多次调用会重复返回同一元素 - 如果底层数据可能并发修改,需考虑加锁或用线程安全容器,否则
ConcurrentModificationException仍会发生
Iterator 和 Stream 在受限访问中的取舍
有人倾向用 Stream 链式过滤再 limit(),但这是错的:它只限制输出数量,不阻止调用方反复调用 stream() 获取新流,也无法防止 count() 或 toList() 泄露总量。
-
Iterator是单次、有状态的;Stream是无状态、可重复构建的——后者天然不适合“受限”语义 - 若必须用
Stream,得配合自定义Spliterator,但复杂度陡增,且多数 JDK 版本不支持对外隐藏estimateSize() - 简单场景下,
Iterator更轻量、意图更清晰;复杂过滤+分页建议用专门的PageableIterator类,而非硬塞进标准接口
容易被忽略的边界:remove() 和 forEachRemaining()
很多人只重写 hasNext() 和 next(),却忘了 Iterator 接口还包含 remove() 和 forEachRemaining()。前者默认抛 UnsupportedOperationException,但如果你没显式覆盖,别人反射调用或通过 Collection.forEach() 触发,可能意外绕过你的控制逻辑。
- 明确禁止删除?重写
remove()并抛UnsupportedOperationException - 想禁用批量消费?重写
forEachRemaining(),内部调用受控的next(),而不是直接委托给底层迭代器 - 测试时别只跑
while (it.hasNext()) it.next(),务必试it.forEachRemaining(...)和it.remove()是否按预期失败
真正的受限,是每条路径都收口。漏掉一个方法,就等于留了一扇没锁的窗。

















