ArrayList的add(index, object)方法会抛出IndexOutOfBoundException异常。

3
使用下面的代码片段创建arrayList:
```java List arrayList = new ArrayList<>(); ```
注意:这会创建一个初始大小为10的空列表。
List arrayList = new ArrayList(16);

ArrayList的内部实现会创建一个大小为16的数组elementData,并在每个位置上赋值为null。如果执行像arrayList.add(2,"HelloWorld")这样的操作,则会出现IndexOutOfBoundException,因为要添加元素的索引(即2)大于arrayListsize属性。
从JavaDocs中可以清楚地看出,当初始化arrayList时,size属性被初始化为0,并且每次向arrayList添加新元素时都会增加1。
请问有人能解释一下,为什么首先要以这种方式设计ArrayList数据结构。尽管在创建arrayList时,内部数据结构elementData已经初始化了16个null值,但它仍不允许在索引> size的位置添加值;(在这种情况下假设索引<16)。实现add(index,object)功能受arrayListsize属性控制的想法是什么?
5个回答

2
拥有大小大于 List.size() 的内部数组的目的是为了避免不必要地重新分配数组。如果内部数组始终与 List 大小相同,则每次添加新元素时,都必须重新分配内部数组,从而导致性能下降。

那么为什么ArrayList不允许在索引大于大小属性的位置存储值呢? - gaurs
2
仅仅因为列表实现不允许这样做。列表不应被视为数组的简单封装。它是一种独立的数据结构。仅仅因为它在内部使用了一个数组,并不意味着可以对其执行相同的操作。 - Petter
但这不是特定于 LinkedList 而不是一般的 List 吗? - gaurs
1
不是的,请看 http://docs.oracle.com/javase/7/docs/api/java/util/List.html#set%28int,%20E%29。Javadocs 指定如果索引大于 size()set 方法应该会抛出 IndexOutOfBoundsException 异常。 - Petter

1
事实上,ArrayList的默认构造函数会构造一个初始容量为10的列表。
public ArrayList() {
    this(10);
} 

但是我们为什么需要这样的分配呢?正如您所理解的那样,如果您提前指定ArrayList的大小,可以为列表提供高效。否则,在元素数量超过ArrayList的初始容量之后,将为每个元素执行新的重新分配操作。 文档上说: public void add(int index, E element) Throws: IndexOutOfBoundsException - if the index is out of range (index < 0 || index > size())
正如您所看到的,如果(index>size()),它会抛出IndexOutOfBoundsException异常。由于ArrayList的“ public int size()”返回不等于null的元素,因此您的大小等于0(而不是您在示例中所说的16)。换句话说,如果还计算了null值,则使用默认构造函数创建的每个ArrayList的大小都为10。
因此,“arrayList.add(2,“HelloWorld”)”会引发IndexOutOfBoundsException异常,因为index = 2但size()= 0。
编辑:
我认为当您构建您的参数时,将此作为基础:
String[] arr = new String[5];
arr[3] = "hello";

System.out.println(arr[3]); 

然后,您想知道为什么可以直接在数组元素中赋值,但是使用ArrayList的add(int index, E element)方法时为什么不能做同样的事情。实际上,这是正确的,但没有条件将ArrayList实现为Array的完整对应物。换句话说,由于ArrayList的本质,此方法受到该规则的限制。众所周知,创建数组时,您需要在方括号中指定其大小。以int为参数的ArrayList构造函数不会执行相同的操作。它只执行一次虚拟分配。是的,它可以使用此分配指定其初始大小,或者在调用add(int index, E element)之后,可以将大小增加一个。但是,ArrayList被实现为提供类似于数组的结构,该结构具有与索引编号相关的连续性,但没有固定大小。因此,还有其他更高级别的抽象示例可以执行此任务。例如,LinkedHashMap结构。

@Arslan:如果这个检查的唯一目的是正确返回arrayList中元素的数量,那么这也可以在不抛出异常的情况下实现。由于每次调用add()时都会增加size属性,它将始终返回arrayList中元素的数量,而不管插入元素的索引位置如何。 - gaurs
我写了官方文档中提到的定义。是的,它在那些条件下会抛出异常。我无法完全理解你的论点。 - Dorukhan Arslan
抱歉表述不清;正如您所提到的(引用javadoc),size属性用于计算arrayList中非空值的数量(考虑默认的10个大小的elementData内部数组); 我的问题是,由于只有在将元素添加到arrayList时才会增加size(无论位置如何; 如果我add(3,“hello”)add(“hello”),size都会增加),为什么我的add(index,object)受到规则的支配,即index <0 || index> size() - gaurs
由于add(“hello”)方法是逐个添加元素的,因此大小会增加一个。我编辑了我的答案,请参见上文。 - Dorukhan Arslan
@Arslan:讲解得非常清楚,点赞+1。非常感谢! - gaurs

0

在特定的索引处添加对象之前,必须先确保该位置为null。您只需要使用add方法添加对象,然后可以在索引上更新值。

例如:

 ArrayList<Integer> arrlist = new ArrayList<Integer>(5);

// use add() method to add elements in the list
arrlist.add(15);
arrlist.add(22);
arrlist.add(30);
arrlist.add(40);

// adding element 25 at third position
arrlist.add(2,25);

0

请查看ArrayList的Java文档中的add(int index, E element)方法。在这里,您可以找到当索引超出范围(index < 0 || index > size())时发生的ArrayIndexOutOfBound异常。

您声明了一个初始容量为16的ArrayList。这并不意味着ArrayList的每个16个索引位置都包含元素。它只是提到了初始容量,当需要时它会动态增加其大小。

请查看ArrayList类的构造函数的源代码 -

/**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param   initialCapacity   the initial capacity of the list
     * @exception IllegalArgumentException if the specified initial capacity
     *            is negative
     */
    public ArrayList(int initialCapacity) {
    super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
           this.elementData = new Object[initialCapacity];
    }

在这里我们找不到任何告诉我们 - ArrayList 将被初始化为 null 值。

更新:根据您的评论,我进行了一些实验,因为我不确定引用/非原始类型的数组是否会被初始化为 null。请参见下面的代码。通过取消每次执行时的注释来运行代码 -

import java.util.List;
import java.util.ArrayList;

public class ArrayListTest{

    public static void main(String[] args){

        List<String> list1 = new ArrayList<String>(); //default with initial capacity 10
        List<String> list2 = new ArrayList<String>(5);
        List<String> list3 = new ArrayList<String>(5);

        list2.add(null);
        list2.add(null);
        list2.add(null);

        list3.add("zero");
        list3.add("one");
        list3.add("two");

        //System.out.println(list1.get(4)); //IndexOutOfBoundException

        //System.out.println(list2.get(0)); //null
        //System.out.println(list2.get(2)); //null;
        //System.out.println(list2.get(3)); //IndexOutOfBoundException

        //System.out.println(list3.get(0)); //zero
        //System.out.println(list3.get(2)); //two;
        //System.out.println(list3.get(3)); //IndexOutOfBoundException
        //list3.add(4, "four"); //IndexOutOfBoundException

    }

}

在这里,你可以看到list2.get(0)list2.get(2)返回了空。因为我们在这些索引处放置了null。但是list2.get(3)不会返回null,因为我们没有在索引3处放置null。因此,似乎引用/非基本类型的数组不会使用null进行初始化。 list2.get(3)会发生IndexOutOfBoundException异常。

您可以在list2的索引2处插入一些值。因为我已经手动将null插入了该列表的索引2处。在list3中找到了相同的情况,其中我没有在该列表中放置任何null。即使我们试图将某些值添加到list3的索引4处,它也会返回IndexOutOfBoundException。因为索引4对于list3不可用。

长话短说,new ArrayList<SomeType>(givenSize) 不会使用 null 初始化一个大小为 givenSize 的数组。

希望能有所帮助。
谢谢。


语句 this.elementData = new Object[initialCapacity]; 将创建一个大小由 initialCapacity 定义的数组,其中所有插槽都将初始化为 null。这是创建非原始类型对象时的默认行为。我的问题是,即使我们有一个底层数据结构被初始化的数组,为什么我们不能在任何位置设置值? 如果我错了,请纠正我! - gaurs
@Sumit,我进行了一个小实验,因为我不确定这个。你可以看到更新部分。似乎new Object [initialCapacity]不会创建一个大小为initialCapacity的数组,并将所有这些插槽初始化为null。但是如果一个数组被初始化为null(必须手动完成),那么我们可以在该位置设置值。谢谢。 - Razib
感谢更新。如问题陈述中所述,每当对大于“size”属性的“index”执行操作(addget)时,都会抛出IndexOutOfBoundException(这与您的程序显示的行为类似)。在ArrayList中,底层数组(elementData)初始化为10个空值(当然,在默认容量情况下),您也可以通过调试ArrayList代码进行验证。 - gaurs

0

你很少需要指定 ArrayList容量,只有当你知道你的 ArrayList 将要容纳多少元素时,它才能提高性能。

ArrayList 只是一个可以自动增长或缩小的 List。使用 List,如果列表为空,你永远不需要在位置 n 添加一个元素,你只需将其与前一个节点链接起来(除非它是头节点)- 这就是 ArrayList 的想法,除了它可以自动增长/缩小。


1
即使我们没有指定arrayList的初始capacity,它会假定一个初始容量为10,这意味着一个大小为10的数组(elementData)已经被创建;但不允许直接在索引> size处添加元素。另一方面,如果我直接创建一个大小为10的数组,我可以在数组中的任何索引处设置值(但不能在arrayList中)。 - gaurs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接