List视图与不可变

1) 先分清三层概念

  • 视图(view) :不拷贝数据、共享底层存储 的"窗口"。对视图或其"父集合"的结构性修改会彼此可见 ,并受 fail-fast 影响(易抛 ConcurrentModificationException)。

  • 不可变的两层含义

    1. unmodifiable(不可修改) :集合接口上的增删改操作直接 UnsupportedOperationException,但底层若被别人改,你仍会看到变化(浅不可变)。

    2. immutable/persistent(真正不可变/持久化)结构性不可变 ,每次"修改"都会返回新集合(结构共享),旧集合永远不变,天然线程安全(结构层面)。

2) 常见"视图"类型与特性

  • List.subList(from, to) :区间视图

    • 共享父 List 的存储与 modCount;对子区间做 set/clear/sort 直接作用在父表相应区间。
    • 与父表交叉修改易触发 fail-fast:先 subList.clear() 再对父表 add(),再访问 subList → 可能抛 CME。
    • 内存风险 :长时间持有 subList 可能间接保住整个父表 (尤其父是大 ArrayList),导致难以释放。需要独立使用时请 new ArrayList<>(subList) 做快照。
  • Arrays.asList(array)数组视图定长

    • 底层是原生数组的包装 ,允许 set(i,x),但 不允许 add/remove(会抛异常)。
    • 数组与 List 同步变化:改数组会反映到 List,反之亦然(限 set)。
    • 常见坑:Arrays.asList(int[]) 会被当成一个元素 ;要使用 装箱类型 或 IntStream.boxed()。
  • Collections.unmodifiableList(list)不可修改的视图

    • 只是包了一层只读代理 ;若底层 list 被别人改,该"只读视图"看到的内容也会变(浅不可变)。
    • 读多团队共享时,除非保证没人能持有原可变引用,否则不是严格意义的"不可变"。
  • Collections.synchronizedList(list)加锁视图

    • 对单次方法调用做内部同步;遍历时仍需手动 synchronized(list){...}

    • 是"并发包装",不是不可变;仍共享底层。

3) 真正意义的"不可变"集合

  • Java 9+ List.of(...) / List.copyOf(...) :返回不可修改的集合实现 ;不接受 null;不会随外部变化;copyOf 会制作不可变副本

  • Guava ImmutableList :构建后永久不可变(浅不可变,元素本身若可变仍可变)。

  • Kotlin 持久化集合 :kotlinx.collections.immutable 的 persistentListOf()/toPersistentList(),结构共享 + 每次修改返回新实例,JVM/Android 可用,适合并发读多写少。

术语小结:unmodifiable = 不允许通过这个引用改;immutable/persistent = 结构真的不可变且修改返回新对象。

4) Kotlin 专项:ListvsMutableList

  • 接口层面 :List 是只读接口 ,MutableList 可变;是否真不可变取决于实现 。把一个 MutableList 赋给 List 变量,只是读接口受限 ,若仍持有原 MutableList 引用,底层仍会变

  • 构建建议****

    • 需要"读接口 + 不被外部意外改":val ro: List = someMutableList.toList()(拷贝一个新数组)。

    • 需要真正结构不可变:用 persistentListOf() 或 toPersistentList()。

    • listOf(...) 返回只读视图语义的实现;不要依赖强制 cast 去写入。

5) 并发、fail-fast 与视图的交互

  • 视图共享底层跨引用修改很容易让 modCount 不一致;随后对任一视图迭代就可能 CME。

  • unmodifiable 视图 只是不许你从这个引用改;别人改了底层你仍然"看得到变化",也可能在迭代时被 fail-fast。

  • persistent/immutable 集合 没有这个问题:每次"修改"都产出新结构,老结构永远一致(弱点是修改时会分配新对象,但共享大部分结构,复杂度通常是 O(log n))。

6) 性能与内存对照

类型 是否拷贝 修改成本 迭代安全 适用场景
subList 视图 ❌(共享) 改子区间 O(k) 易受 fail-fast 影响 区间处理、局部排序、临时窗口
Arrays.asList 视图(定长) ❌(共享) 仅 set 不改结构即可 适配旧 API、快速包装
unmodifiableList 视图 ❌(共享) 禁改 受底层影响 对外只读暴露(需确保外部无原引用)
List.of/ImmutableList ✅(构建时拷贝) 不可改 结构稳定 配置/常量/并发读
Kotlin persistentList 结构共享(局部节点新建) 修改返回新对象 结构稳定 并发/快照、多版本回溯

7) 实战最佳实践

  1. 对外返回集合

    • Java:return Collections.unmodifiableList(new ArrayList<>(internal));(先拷贝再只读包装)。
    • Kotlin:fun expose(): List = internal.toList();需要强不可变则 toPersistentList()。
  2. 只用视图做短期操作(排序/清理一段)

    • list.subList(i, j).clear() 或 Collections.sort(list.subList(i, j)),用完即丢,不要长持。
  3. 避免 Arrays.asList 当可变表

    • 需要增删:new ArrayList<>(Arrays.asList(...));或 array.toMutableList()(Kotlin)。
  4. 团队接口契约

    • 输入参数 :若函数需要保留快照,立即拷贝(防止调用方后续修改影响内部)。
    • 返回值:约定"只读"还是"真正不可变";并在文档中说明是否会随内部变化而变化。
  5. 并发读写

    • 避免"视图 + 另处写"组合;需要并发读多写少:CopyOnWriteArrayList(快照迭代、不抛 CME)或持久化集合

    • 需要强一致:外部同步或消息化(避免共享可变集合)。

8) 代码模版

A. 安全对外暴露(Java)

swift 复制代码
public final class Repo {
  private final List<Item> items = new ArrayList<>();
  public List<Item> snapshot() {
    return Collections.unmodifiableList(new ArrayList<>(items)); // 拷贝 + 只读
  }
}

B. Kotlin:只读或真正不可变

kotlin 复制代码
class Repo {
  private val _items = mutableListOf<Item>()
  fun readOnly(): List<Item> = _items.toList()                 // 拷贝快照
  fun immutable(): PersistentList<Item> = _items.toPersistentList() // 真正不可变
}

C. subList 用后即焚

ini 复制代码
List<Item> window = list.subList(100, 200);
Collections.sort(window, cmp);
window.clear();    // 清理区间
// 之后不要再持有 window 变量

D. Arrays.asList 正确姿势

ini 复制代码
String[] arr = {"a", "b"};
List<String> fixed = Arrays.asList(arr); // 定长
fixed.set(0, "A");     // OK,反映回 arr
// fixed.add("c");     // ❌ UnsupportedOperationException
List<String> growable = new ArrayList<>(fixed); // 可增删

TL;DR(面试/落地小抄)

  • subList/Arrays.asList/unmodifiableList 都是"视图" :共享底层,不等于真正不可变,会受 fail-fast 约束。
  • 需要稳定不变 :Java 用 List.of/copyOf 或 Guava ImmutableList;Kotlin 用 persistentList
  • 对外暴露集合拷贝 + 只读包装 是默认安全做法;视图只做短期操作。
  • 并发场景:读多写少用 CopyOnWriteArrayList 或持久化集合;需要强一致靠同步或消息化,别指望"只读视图"解决并发一致性。
相关推荐
绝无仅有3 小时前
面试技巧之Linux相关问题的解答
后端·面试·github
绝无仅有3 小时前
某跳动大厂 MySQL 面试题解析与总结实战
后端·面试·github
道可到3 小时前
阿里面试原题 面试通关笔记05 | 异常、泛型与反射——类型擦除的成本与优化
java·后端·面试
晴殇i3 小时前
告别 localStorage!探索前端存储新王者 IndexedDB
前端·javascript·面试
干翻秋招3 小时前
Java知识点复习
后端·面试
聪明的笨猪猪5 小时前
Java Spring “MVC ”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
007php00714 小时前
某大厂跳动面试:计算机网络相关问题解析与总结
java·开发语言·学习·计算机网络·mysql·面试·职场和发展
倔强青铜三14 小时前
苦练Python第63天:零基础玩转TOML配置读写,tomllib模块实战
人工智能·python·面试
倔强青铜三15 小时前
苦练Python第62天:零基础玩转CSV文件读写,csv模块实战
人工智能·python·面试