news 2026/10/6 1:54:36

Java集合与泛型:从Arrays.asList的异常,看清视图、复制和类型边界

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java集合与泛型:从Arrays.asList的异常,看清视图、复制和类型边界

我原来的第三篇笔记覆盖 ArrayList、LinkedList、Map、引用与泛型,但有些短结论容易让人用错。比如“LinkedList 删除快”,漏了怎么找到节点;“asList 返回列表”,漏了列表是否能增长。

这次以JDK 17为基线,先跑一个会产生异常的小程序,再把几个概念分开。并未测吞吐、对象大小或 GC 时机,不把 API 行为实验写成性能排名。

1. Arrays.asList:能set,为什么不能add?

原文写成 ArrayList 的 asList,实际上是 Arrays.asList。它返回由原数组支持的固定大小列表,不是一个可自动增长的 java.util.ArrayList。Arrays API。

下面完整程序分别观察视图和复制,文件名用 ListViewProbe.java:

importjava.util.*;publicclassListViewProbe{publicstaticvoidmain(String[]args){String[]array={"a","b"};List<String>view=Arrays.asList(array);List<String>copy=newArrayList<>(view);view.set(0,"x");System.out.println(Arrays.toString(array));array[1]="y";System.out.println(view);System.out.println(copy);try{view.add("z");thrownewAssertionError("add unexpectedly succeeded");}catch(UnsupportedOperationExceptionexpected){System.out.println("view cannot grow");}copy.add("z");System.out.println(copy);int[]primitive={1,2};List<int[]>oneElement=Arrays.asList(primitive);System.out.println(oneElement.size());System.out.println(oneElement.get(0)==primitive);}}

实际输出:

[x, b] [x, y] [a, b] view cannot grow [a, b, z] 1 true
对象与原数组共享元素槽位?能增删长度?
Arrays.asList(array)是否
new ArrayList<>(view)容器不共享这些槽位是

第二种是容器复制,不是元素深拷贝。若元素本身是可变对象,两份容器仍可能引用同一对象。int[] 传给 asList 则作为一个数组对象元素,不会自动逐项装箱成 List。

2. LinkedList删除快:先把定位成本说出来

ArrayList remove(i):定位下标 -> 移动后续元素 LinkedList remove(i):沿链找到位置 -> 改前后链接

ArrayList 按下标 get 通常是 O(1),中间删除需要搬移,尾部删除是例外;尾部 add 的均摊成本为 O(1),扩容那一次仍需复制。
LinkedList 按下标访问需要从较近一端遍历,通常不能把 remove(i) 的整体成本写成 O(1)。已通过迭代器定位后的局部修改,和每次从下标重新查找,不是同一工作量。ArrayList、LinkedList。

实际选型还看访问模式、节点开销与缓存局部性,不能凭“链表不用搬运”断定它在所有数据规模下更快。本篇没有做基准测试。

3. Collection是接口,Collections是工具

Collection(接口) +-- List(接口) -> ArrayList、LinkedList(实现类) +-- Set(接口) -> HashSet(实现类) +-- Queue(接口) -> Deque(接口) Map 是另一条接口分支,不继承 Collection Collections 是工具类,不是上面的父接口

这是简化关系图,不列全部继承路径。LinkedList 也实现 Deque,因此不能把所有集合强行放进互不相交的三个盒子。原文把 ArrayList、LinkedList 当成 Collection 的“子接口”,应改为实现类。

4. Map:null规则与并发保证分开看

实现null键/值使用时的边界
HashMap允许不提供并发访问的整体安全保证
Hashtable都不允许方法同步,不代表任意多步组合都原子
ConcurrentHashMap都不允许并发容器,复合需求应选有相应保证的方法

这里不是性能排序,也不把旧版本的“分段锁”当作 JDK 17 的通用结构答案。举例:get 后再 put 两次独立调用之间仍可能被其他线程穿插;要对一个键累加,可用有原子更新语义的 merge,而不是凭容器线程安全就认为 get+put 自动成为一次操作。ConcurrentHashMap API。

importjava.util.*;importjava.util.concurrent.ConcurrentHashMap;publicclassMapProbe{publicstaticvoidmain(String[]args){Map<String,Integer>map=newHashMap<>();map.put("known",null);System.out.println(map.get("known")==null);System.out.println(map.get("missing")==null);System.out.println(map.containsKey("known"));System.out.println(map.containsKey("missing"));ConcurrentHashMap<String,Integer>concurrent=newConcurrentHashMap<>();concurrent.merge("hits",1,Integer::sum);concurrent.merge("hits",1,Integer::sum);System.out.println(concurrent.get("hits"));}}

输出依次为 true、true、true、false、2。HashMap 的 get 返回 null,不足以区分“键不存在”和“键存在、值为 null”。此程序是顺序 API 演示,不是并发压测;原子性依据 API 保证,不能由两次顺序调用证明。

5. 泛型不是“子类集合自动变成父类集合”

Integer 是 Number 的子类,但 List 不是 List 的子类型。下面的故意错误代码应编译失败:

importjava.util.*;classBadGenericAssignment{voidtest(){List<Integer>integers=newArrayList<>();List<Number>numbers=integers;}}

如果允许这个赋值,就可以通过 numbers 添加 Double,让原本声明只装 Integer 的列表混入另一种类型。泛型的编译期检查正是在阻止这个问题。JLS类型与泛型。

只需要读取 Number,可以用 extends;需要接收 Integer 写入,可以用 super。下面是完整的类型安全复制例子:

importjava.util.*;publicclassGenericCopyProbe{static<T>voidcopy(List<?extendsT>source,List<?superT>target){for(Titem:source)target.add(item);}publicstaticvoidmain(String[]args){List<Integer>source=List.of(1,2);List<Number>target=newArrayList<>();copy(source,target);System.out.println(target);}}

输出 [1, 2]。泛型提高类型安全,不保证调用永远不抛异常:目标若是固定大小或不可修改列表,add 仍可能失败。类型边界与对象行为是两回事。

6. 原笔记的四种引用,补上时间边界

强可达对象不会因为“内存不足”就直接作为垃圾回收;这不等于永远不会回收。软、弱引用影响可达性判定,但不能据此承诺对象在某一行代码后立刻被回收。虚引用的 get 始终返回 null,结合队列观察引用处理,不是取回对象做一次普通调用。引用包说明、PhantomReference。

资源关闭优先使用明确生命周期与 try-with-resources,不把 GC 时机当成可靠的资源释放时钟。本次没有用 System.gc() 加等待来制造“确定回收”的结论。

7. 本次怎么验证

本地 JDK 17 实际编译正文三个可运行程序,核对输出和异常;泛型错误程序必须编译失败,另外验证接口继承关系与容器浅复制。未进行性能排名、内存布局测量或 GC 时序测试。

复习集合时,我现在更愿意追问三个问题:返回的是视图还是复制?快的是定位还是修改?保证的是单个操作还是整个业务步骤?把边界讲出来,才不是背了几句特点却仍然用错。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 1:50:47

SQL列重命名:AS改的是结果表头,不是原表字段

牛客 SQL5要求查看前两个用户的设备 ID&#xff0c;并把结果列名改为 user_infos_example。 我原来的笔记只说“用了 AS 和 LIMIT”&#xff0c;还没有把两个动作讲清楚。这里其实是两个独立要求&#xff1a;取多少行&#xff0c;与结果叫什么名字。 1. 把题目翻译成 SQL 按…

作者头像 李华
网站建设 2026/10/6 1:50:46

SQL学校筛选:等于北京大学,为什么不能随手换成LIKE?

牛客 SQL6要求筛选北京大学的学生&#xff0c;返回设备 ID 和学校。原稿答案是正确的&#xff1a; SELECT device_id, university FROM user_profile WHERE university 北京大学;但“WHERE 用来筛选”只是第一步。更值得问的是&#xff1a;筛选条件表达的业务范围是什么&…

作者头像 李华
网站建设 2026/10/6 1:46:59

OpCore Simplify 指南:自动化 OpenCore EFI 生成与硬件兼容性配置

OpCore Simplify 指南&#xff1a;自动化 OpenCore EFI 生成与硬件兼容性配置 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify OpCore Simplify 是一款…

作者头像 李华