商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 如何在 Java 中使用 ArrayList 实现可变长度的动态数组

如何在 Java 中使用 ArrayList 实现可变长度的动态数组

  发布于2026-07-11 阅读(0)

扫一扫,手机访问

很多Ja va开发者刚接触 ArrayList 时,都会有个错觉:既然它叫“动态数组”,那是不是天生就能自动扩容、随便用?其实不然——真正需要理解的,不是怎么“实现”它,而是怎么避开它那些隐性开销和并发陷阱。

先看一个最容易被忽略的细节:刚创建的 ArrayList,它的容量和 size 根本是两码事。你以为 new ArrayList() 之后,内部数组就已经就绪了?其实JDK 12以上的版本默认用空数组占位,早期版本给了个长度为10的数组,但 size() 始终返回0。只有当你第一次调用 add(),元素才真正写入,而底层数组的扩容,也是在这个时候触发的。扩容不是一次加一个元素,而是按1.5倍增长——从10跳到15,再跳到22,33……目的是减少复制成本。如果事先能估算出元素数量,最好用 new ArrayList(initialCapacity) 预设容量,少折腾几次扩容。

接下来聊聊 add()set()remove() 这三兄弟的使用差异。add(E) 在末尾追加,简单高效;set(int, E) 是替换指定索引处的已有元素,注意:这个索引必须小于当前的 size(),否则直接抛 IndexOutOfBoundsException。而 remove(int) 删除指定索引元素后,后面所有元素都得向前移动一步——这就意味着O(n)的时间复杂度。如果你频繁在中间位置增删,可能得考虑 LinkedList 或者干脆换一种数据结构(比如 Map)。还有一点容易踩坑:remove() 有重载,一个是 remove(int),一个是 remove(Object)。如果你不小心传了一个整数,编译器会优先匹配 int 版本,导致你以为在删对象,实际却按索引删了。

另一个经典翻车现场是:遍历列表的同时修改它。在 for-each 里直接调用 remove()add(),几乎百分百会触发 ConcurrentModificationException,哪怕是在单线程里。怎么安全操作?用 Iterator.remove() 可以删除当前元素;如果需要边遍历边添加,那就先收集待添加的元素,等循环结束后统一 addAll()。要是多线程环境,ArrayList 本身就不是线程安全的,别指望加个 synchronized 就能应付——改用 Collections.synchronizedList() 或者 CopyOnWriteArrayList(后者适合读多写少的场景)。

最后说一个 toArray() 的常见误区。很多人写 list.toArray(new String[list.size()]),觉得“我给了正好大小的数组,多省事”。但事实上,推荐写法是 list.toArray(new String[0])——传入一个长度为0的数组,让 ArrayList 内部自己分配一个大小合适的数组返回。为什么?因为类型擦除可能导致运行时返回 Object[],尤其是泛型类型不是 String 时。JDK 11 以后有更直观的方式:list.toArray(String[]::new)。千万别用无参的 list.toArray() 然后强转——它永远返回 Object[],强行转换就是 ClassCastException

其实说到底,ArrayList 本身并不难用,难的是判断什么时候不该用它。频繁插入删除、需要排序保证、元素量级达到百万且内存敏感、或者明确要求不可变——这些场景下,ArrayList 的默认行为反而成了性能瓶颈。理解它的底层机制,才能在正确的地方做出正确的选择。

本文转载于:https://www.php.cn/faq/2380313.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注