
1. ArrayList基础操作全解析从字符串到自定义对象ArrayList作为Java集合框架中最常用的动态数组实现几乎出现在所有Java开发者的日常编码中。今天我们就来深入探讨ArrayList对字符串和自定义对象的存储、修改、删除及遍历操作。这些基础操作看似简单但其中隐藏着不少性能陷阱和最佳实践。先看一个最基础的字符串存储示例ArrayListString strList new ArrayList(); strList.add(Java); strList.add(Python); strList.add(C);1.1 核心API操作原理ArrayList的底层实现是一个Object[]数组当添加元素时检查当前数组容量是否足够不足时进行扩容通常扩容为原来的1.5倍将元素添加到数组末尾这种实现方式决定了它的几个重要特性随机访问速度快O(1)复杂度尾部插入效率高中间插入/删除效率较低需要移动后续元素重要提示初始化时如果知道大概的元素数量应该使用带初始容量的构造函数避免频繁扩容带来的性能损耗。2. 存储自定义对象的完整实践存储自定义对象是ArrayList更常见的用法。假设我们有一个Student类public class Student { private String name; private int age; // 构造方法、getter/setter省略... }2.1 对象存储的正确姿势存储自定义对象时要注意重写equals()和hashCode()方法考虑实现Comparable接口以便排序对于要频繁修改的对象注意线程安全问题添加对象示例ArrayListStudent studentList new ArrayList(); studentList.add(new Student(张三, 20)); studentList.add(new Student(李四, 22));2.2 对象修改的两种方式修改ArrayList中的对象有两种典型场景直接通过索引获取并修改Student s studentList.get(0); s.setName(王五);使用set()方法替换整个对象studentList.set(0, new Student(王五, 20));第一种方式更高效但要注意如果对象被多个地方引用可能会产生副作用。第二种方式更安全但会产生更多垃圾对象。3. 删除操作的陷阱与解决方案ArrayList的删除操作可能是最容易出问题的部分常见的有以下几种方式3.1 按索引删除studentList.remove(0); // 删除第一个元素3.2 按对象删除Student toRemove new Student(李四, 22); studentList.remove(toRemove); // 依赖equals()方法3.3 遍历时删除的坑最常见的错误是在for循环中直接删除for(int i0; istudentList.size(); i) { if(/*某些条件*/) { studentList.remove(i); // 这会导致后续元素索引变化 i--; // 需要手动调整索引 } }正确做法是使用迭代器IteratorStudent it studentList.iterator(); while(it.hasNext()) { Student s it.next(); if(/*某些条件*/) { it.remove(); // 安全的删除方式 } }或者使用Java8的removeIfstudentList.removeIf(s - /*谓词条件*/);4. 遍历的四种方式与性能对比ArrayList提供了多种遍历方式各有优缺点4.1 传统for循环for(int i0; istudentList.size(); i) { Student s studentList.get(i); // 处理s }优点可以通过索引随机访问缺点对于LinkedList等实现效率低4.2 增强for循环for(Student s : studentList) { // 处理s }优点语法简洁缺点不能修改集合结构4.3 迭代器方式IteratorStudent it studentList.iterator(); while(it.hasNext()) { Student s it.next(); // 处理s }优点可以安全删除元素缺点语法稍显复杂4.4 Java8 forEachstudentList.forEach(s - { // 处理s });优点函数式风格简洁缺点调试不太方便性能测试表明对于ArrayList传统for循环通常是最快的但在大多数应用场景中差异不大。选择哪种方式更多取决于具体需求和代码风格。5. 实战经验与性能优化在实际项目中使用ArrayList时我总结了一些经验教训初始化容量如果能预估元素数量初始化时指定容量可以避免多次扩容。例如ArrayListString list new ArrayList(1000);批量操作使用addAll()比多次add()更高效list.addAll(Arrays.asList(a, b, c));避免频繁修改如果需要频繁在中间位置插入/删除考虑使用LinkedList线程安全ArrayList不是线程安全的多线程环境下应该使用ListString syncList Collections.synchronizedList(new ArrayList());或者考虑CopyOnWriteArrayList空间回收对于长期存在且可能变小的ArrayList适时trimToSize()可以节省内存list.trimToSize();元素查找contains()和indexOf()方法的时间复杂度是O(n)对于大型集合要考虑性能影响子列表陷阱subList()返回的视图会反映原列表的修改使用时要注意ListString sub list.subList(0, 5); list.add(new); // 会导致sub列表操作抛出ConcurrentModificationException6. 常见问题排查在实际开发中我遇到过不少ArrayList相关的问题这里分享几个典型案例问题1ConcurrentModificationException异常场景在遍历集合时同时修改它解决方案使用迭代器的remove()方法或者先收集要删除的元素最后统一删除问题2性能突然下降场景大型ArrayList频繁在头部插入解决方案改为尾部插入或使用LinkedList问题3内存泄漏场景长期持有不再使用的大型ArrayList解决方案及时清空或缩小容量问题4元素顺序混乱场景多线程环境下未经同步直接修改解决方案使用线程安全集合或手动同步问题5contains()返回错误结果场景自定义对象未正确实现equals()解决方案确保重写了equals()和hashCode()7. 与其它集合的对比选择虽然本文聚焦ArrayList但合理选择集合类型很重要集合类型适用场景不适用场景ArrayList随机访问多修改少频繁在中间插入/删除LinkedList频繁插入/删除随机访问多HashSet快速查找去重需要保持插入顺序TreeSet需要排序内存敏感场景HashMap键值对存储需要有序遍历对于字符串存储如果不需要重复元素考虑使用HashSet如果需要保持插入顺序LinkedHashSet可能更合适。