Java ArrayList 不为人知的陷阱,及add(int index, E element)和set(int index, E element)两个方法的说明

本文通过一个具体的例子详细解析了Java中ArrayList的使用误区,包括初始化、size属性的理解、set与add方法的区别等,揭示了在特定情况下可能出现的异常及原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一般使用List集合,估计都是使用这个ArrayList,一般呢也就是简单遍历数据和存储数据。
很少使用到add(int index, E element)和set(int index, E element)两个方法。
这两个方法,乍一看,就是在指定的位置插入一条数据。
区别:
set()是更新,更新指定下标位置的值。
add()是添加,区别于一般的add(E e),这个就是有个位置的概念,特殊位置之后的数据,依次往后移动就是了。

然后,看下面代码。来看看陷阱。

就算是,你知道了上面的内容,也不能保证,你能安全的回答下面的代码运行结果。

public class ArrayListTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(2);
        System.out.println("list大小为:" + list.size());
        list.add("12");
        System.out.println("list大小为:" + list.size());
        list.set(1,"sss");
        list.add(1,"放在下标为1的位置");
        list.add("22");
        System.out.println("list大小为:" + list.size());
        System.out.println(list.toString());
        list.add(1,"放在下标为1的位置");
        System.out.println("list大小为:" + list.size());
        System.out.println(list.toString());
    }
}

运行结果:

估计,很多人都大出所料吧。结果怎么是这样呢!下面有这么几个坑。
解释:
1,List<String> list = new ArrayList<>(2);
这个乍一看,还以为像数组一样,你初始化了个长度为2的数组,然后你就可以随意修改下标为0,1的内容。当然0,1是肯定不会下标越界的。
我当时也是这么想的。然而不是的。
源码里面的构造函数的注释说明:这个真的是在底层新建了个长度为2的数组Object[],但是他又不是我们经常看到的一般数组。这个是空的。
当前list集合仍然是一个带有初始容量的empty list。
2,list.size()
我上来也是想当然的以为,这个size()不就是上面的那个2吗!但是我错了。
源码中ArrayList的size属性对应的注释是:The size of the ArrayList (the number of elements it contains).
 这个size是表示这个集合包含的元素的个数,而不是底层数组的大小。
 3,list.set(1,"sss");
在知道了上面的2个坑之后,这个就好解释了。代码是要设置下标为1的值,但是。在源码中有 rangeCheck(index); 

    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

这个时候,index是1,但是size是1。
so,就异常啦。
4,list.add(1,"放在下标为1的位置");
这个和上面相似,但还是有点不一样,上面判断是大于等于size,但是在这个add方法,却是要大于才可以。
而且还有个专门的check方法for add,如下:rangeCheckForAdd(index);

    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

那,这个在下标为1的位置,添加元素,判断 1 > 1 不成立,那么就可以顺利添加了。这个就不异常啦。
但是,你要是在数组声明完,直接在下标为1的位置,这么干,就异常了。就不在继续分析了。
5,还有个问题,就是你在一个位置一直add(index , element)。这个不仅仅是把index位置的数据给替换啦,而且把之前这个位置的数据给依次向后移动啦,这个跟map的结构不一样,map是更新指定位置的数据,其他的数据不动,这个list竟然是把把数据平移啦。。。。。

<think>嗯,用户问的是JDK 8中AbstractListadd方法为什么会抛出UnsupportedOperationException异常,以及子类如何实现。首先,我得回忆一下Java集合框架的结构。AbstractList是一个抽象类,提供了List接口的基本实现,但有些方法没有具体实现,比如addset、remove,直接调用会抛异常。这可能是因为AbstractList作为基类,无法确定子类的具体数据结构,比如是否支持动态修改。比如,Arrays的内部类ArrayList是固定大小的,所以它没有重写这些方法,导致调用add时会抛异常。 然后,用户想知道子类如何重写这些方法。比如,ArrayListLinkedList这些标准集合类肯定重写了这些方法。需要具体说明子类在继承AbstractList时,必须根据自身的数据结构特点来实现这些方法。比如,ArrayListadd方法会检查容量,可能需要扩容,而LinkedList则是操作节点指针。 还需要解释为什么AbstractList默认抛出异常。设计上,AbstractList作为抽象类,允许子类选择性地支持可变操作。有些列表可能是只读的,比如通过Collections.unmodifiableList返回的列表,或者像Arrays.ArrayList这样的定长列表。这样设计可以增加灵活性,子类只需实现它们支持的方法。 可能用户遇到的问题是在使用某些List实现时,比如用Arrays.asList()得到的列表,尝试添加元素会抛出异常,这时候需要解释背后的原理。引用中的内容提到Arrays.ArrayList没有重写addremove方法,因此抛出异常,而标准ArrayList则实现了所有方法。这可能就是用户遇到的情况,需要明确说明两者的区别。 此外,用户可能想知道如何正确扩展AbstractList,比如自己创建的子类需要重写哪些方法,以及如何确保修改次数(modCount)的正确管理,以避免ConcurrentModificationException。比如,在迭代器实现中,modCountexpectedModCount需要同步,这在AbstractList的子类中必须正确处理。 总结回答的结构:先解释AbstractList的设计意图,说明默认方法为何抛出异常;然后举例不同子类的实现方式,如ArrayListLinkedList;接着讨论扩展AbstractList的注意事项,包括必须重写的方法modCount的处理;最后结合引用内容,指出特定实现类如Arrays.ArrayList的行为原因。这样用户不仅能理解异常的原因,还能知道如何正确实现自己的子类,避免常见的错误。</think>### JDK 8 AbstractListadd 方法实现原理 #### 1. AbstractList 的默认设计 `AbstractList` 是 Java 集合框架中 `List` 接口的骨架实现,其 `add(int index, E element)` 方法默认抛出 `UnsupportedOperationException` 异常,源码如下: ```java public void add(int index, E element) { throw new UnsupportedOperationException(); } ``` **设计意图**: - **灵活性**:`AbstractList` 允许子类选择性地支持可变操作。例如,某些列表(如 `Arrays.ArrayList`)是定长的,无需支持动态修改[^3]。 - **强制子类显式实现**:若子类需要支持动态操作(如添加、删除),必须主动重写这些方法,避免误用默认实现。 --- #### 2. 子类如何重写实现 子类需根据自身数据结构特性重写 `add` 方法。以下是两种典型实现: ##### 示例 1:ArrayList 的实现 `ArrayList` 通过动态数组实现,其 `add` 方法会扩容数组并插入元素: ```java public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // 扩容检查 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 数据迁移 elementData[index] = element; size++; } ``` **关键点**: - 通过数组拷贝实现元素插入。 - 扩容逻辑确保容量足够[^4]。 ##### 示例 2:LinkedList 的实现 `LinkedList` 通过链表实现,其 `add` 方法直接操作节点指针: ```java public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); // 插入尾部 else linkBefore(element, node(index)); // 插入中间 } ``` **关键点**: - 时间复杂度为 $O(n)$(需遍历链表定位节点)。 - 无需扩容,仅调整指针。 --- #### 3. 扩展 AbstractList 的注意事项 若自定义类继承 `AbstractList`: 1. **必须重写可变操作**:例如 `add`、`remove`,否则调用时会抛异常[^1]。 2. **同步修改计数(modCount)**:在结构修改时更新 `modCount`,防止迭代过程中出现 `ConcurrentModificationException`。 --- ### 总结 `AbstractList` 的默认设计通过抛出异常强制子类明确是否支持动态修改。子类需根据数据结构特性实现 `add` 方法,并管理内部状态(如容量、指针、修改计数)[^4]。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值