什么导致java.lang.ArrayIndexOutOfBoundsException错误,如何预防它?

356

ArrayIndexOutOfBoundsException是什么意思,我该如何解决它?

以下是触发异常的代码示例:

String[] names = { "tom", "bob", "harry" };
for (int i = 0; i <= names.length; i++) {
    System.out.println(names[i]);
}

1
关于上一个问题,提供代码会很有帮助。你是使用已知的索引访问数组,还是需要开始调试以确定在出现错误时索引是如何计算的? - justkt
51
将“i <= name.length”替换为“i < name.length”,或者更好的做法是使用增强型for循环(“for (String aName : name) { ... }”)。 - Jean Hominal
2
这意味着你想获取不存在的数组元素,'i<=name.length' 意味着你想获取长度为 length+1 的元素 - 它不存在。 - hbk
1
https://en.wikipedia.org/wiki/Off-by-one_error - sakhunzai
当您尝试操作的索引超过数组长度时,数组会越界。为了正确性,您的索引应始终比数组元素的总数少一个,因为数组索引从0开始而不是1。 - Jyotirmay
27个回答

345

你需要首先查看文档,其中已经比较清楚地解释了:

抛出此异常表示使用非法索引访问了数组。索引要么为负数,要么大于等于数组大小。

例如:

int[] array = new int[5];
int boom = array[10]; // Throws the exception

关于如何避免这种情况...嗯,不要那样做。小心处理数组下标。

有时人们会遇到一个问题,认为数组是从1开始的,例如:

int[] array = new int[5];
// ... populate the array here ...
for (int index = 1; index <= array.length; index++)
{
    System.out.println(array[index]);
}

这段代码将会忽略第一个元素 (索引 0),当索引为 5 时会抛出异常。有效的索引范围应该是 0-4(包含 0 和 4)。这里正确、惯用的for语句应该是:

for (int index = 0; index < array.length; index++)

(这是假设你当然需要使用索引,在这种情况下。如果可以使用增强型for循环,那就使用它吧。)

(That's assuming you need the index, of course. If you can use the enhanced for loop instead, do so.)


4
针对在Java中可能拥有任意形状的多维数组,我想补充一点:嵌套循环应该检查相关子数组的长度,可采用以下方式:for (int nestedIndex = 0; nestedIndex < array[outerIndex].length; nestedIndex++) { ... array[outerIndex][nestedIndex] ... } - Andrey
在回答中提到应该查看堆栈跟踪以找到错误发生的确切行是很好的。在我看来,这是第一步。 - rghome
@rghome:这并不是回答“ArrayIndexOutOfBoundsException意味着什么”的问题。我认为,在试图理解代码中导致此错误的原因之前,您需要了解它的含义。如果我告诉你在给定的代码行中有GrobulatorError,如果你完全不知道GrobulatorError是什么,你会如何期望找出导致它的原因呢? - Jon Skeet
是的,但我们收到了许多这样的问题,需要找到一个链接来关闭重复问题。通常,问问题者甚至没有确定出现错误的代码行。还有另一个关于如何分析堆栈跟踪的问题,但我认为有一个单独的过程来查找IOOB会很好,这样某人就可以关闭问题并确信问问题者可以通过链接自行解决问题。 - rghome
@rghome:规范的问题和答案听起来不错,但我认为这不一定是正确的。即使它确实是正确的,我仍然会说首先要做的是了解错误的含义,然后再查看您的代码以了解其原因。 - Jon Skeet

61

基本上:

if (index < 0 || index >= array.length) {
    // Don't use this index. This is out of bounds (borders, limits, whatever).
} else {
    // Yes, you can safely use this index. The index is present in the array.
    Object element = array[index];
}

根据您的具体情况,

for (int i = 0; i<=name.length; i++)

索引包括数组的长度。这是越界了。你需要用<替换<=

for (int i = 0; i < name.length; i++)

参见:


29

以下是这篇优秀文章的内容:for循环中的ArrayIndexOutOfBoundsException

简单来说:

在最后一次迭代中,

for (int i = 0; i <= name.length; i++) {
i 的值将等于 name.length,这是一个不合法的索引,因为数组索引是从零开始的。
你的代码应该改为:
for (int i = 0; i < name.length; i++) 
                  ^

19

这意味着你正在尝试访问一个数组的索引,但该索引不在有效范围内。

例如,这将使用上限为4来初始化一个原始整数数组。

int intArray[] = new int[5];

程序员从零开始计数。例如,这个循环会抛出一个 ArrayIndexOutOfBoundsException 异常,因为上限是4而不是5。

intArray[5];

5
界限是指范围内的限制,例如:0-5。 - John Giotta

15

什么导致了ArrayIndexOutOfBoundsException

如果你把一个变量想象成一个“盒子”,你可以在里面放置一个值,那么数组就是一系列相邻放置的盒子,盒子的数量是有限且明确的整数。

创建这样的数组:

final int[] myArray = new int[5]

创建了一个包含5个元素的一维数组,每个元素都是int类型。每个元素都有一个索引,即它在数组中的位置。这个索引从0开始,到N-1结束,其中N是数组的大小(即元素的数量)。

要访问这个数组中的某个元素,可以通过其索引进行引用,例如:

myArray[3]

这将为您提供系列中第4个框的值(因为第一个框的索引为0)。

尝试通过传递高于最后一个“框”的索引或负数的索引来检索不存在的“框”,会导致ArrayIndexOutOfBoundsException

对于我的运行示例,这些代码片段会产生这样的异常:

myArray[5] //tries to retrieve the 6th "box" when there is only 5
myArray[-1] //just makes no sense
myArray[1337] //way to high

如何避免ArrayIndexOutOfBoundsException

为了预防ArrayIndexOutOfBoundsException,有一些关键点需要考虑:

循环

当遍历数组时,始终确保所检索的索引严格小于数组的长度(盒子的数量)。例如:

for (int i = 0; i < myArray.length; i++) {

注意符号 <,不要在其中混合使用 =

你可能想要尝试像这样做:

for (int i = 1; i <= myArray.length; i++) {
    final int someint = myArray[i - 1]

别这样做。如果你需要使用索引,请坚持使用上面的方法,它会为你省去很多麻烦。

在可能的情况下,请使用foreach:

for (int value : myArray) {

这样,您就不必考虑索引。

在循环时,无论做什么,绝对不要更改循环迭代器(此处为i)的值。它的唯一变化应该是为了使循环继续进行。否则,这只会冒着产生异常的风险,在大多数情况下都不是必要的。

检索/更新

在检索数组的任意元素时,始终检查其是否为数组长度的有效索引:

public Integer getArrayElement(final int index) {
    if (index < 0 || index >= myArray.length) {
        return null; //although I would much prefer an actual exception being thrown when this happens.
    }
    return myArray[index];
}

13
为避免数组下标越界异常,应该在可能的情况下使用增强型for语句。
主要动机(和使用场景)是在迭代过程中不需要任何复杂的迭代步骤时。您不能使用增强型for在数组中向后移动或仅迭代每隔一个元素。
这样做可以确保不会缺少要迭代的元素,并且您可以轻松地将已更正的示例转换为增强型for
以下是代码:
String[] name = {"tom", "dick", "harry"};
for(int i = 0; i< name.length; i++) {
    System.out.print(name[i] + "\n");
}

...与此等价:

String[] name = {"tom", "dick", "harry"};
for(String firstName : name) {
    System.out.println(firstName + "\n");
}

9
在你的代码中,你已经访问了从索引0到字符串数组长度的元素。name.length给出了你的字符串对象数组中的字符串对象数量,即3个,但是你只能访问索引2以及以下的元素name[2], 因为数组可以从索引0到name.length - 1进行访问,这里你将获得name.length个对象。
即使在使用for循环时,你也从索引零开始,并应该以name.length - 1结束。在一个数组a[n]中,你可以访问a[0]到a[n-1]。
例如:
String[] a={"str1", "str2", "str3" ..., "strn"};

for(int i=0; i<a.length(); i++)
    System.out.println(a[i]);

根据您的情况:

String[] name = {"tom", "dick", "harry"};

for(int i = 0; i<=name.length; i++) {
    System.out.print(name[i] +'\n');
}

7

对于给定的数组,数组长度为3(即name.length = 3)。但由于它从索引0开始存储元素,因此最大索引为2。

因此,你应该将“i<=name.length”改写为“i<name.length”,以避免“ArrayIndexOutOfBoundsException”异常。


4

对于这个简单的问题就说到这里,但我想强调Java中一个新特性,这将避免即使对于初学者在数组索引方面的所有困扰。Java-8已为您抽象出了迭代的任务。

int[] array = new int[5];

//If you need just the items
Arrays.stream(array).forEach(item -> { println(item); });

//If you need the index as well
IntStream.range(0, array.length).forEach(index -> { println(array[index]); })

什么是好处呢?首先,就像英文一样易读。其次,您无需担心 ArrayIndexOutOfBoundsException


3
我见过最常见的看似神秘的ArrayIndexOutOfBoundsException的情况是同时使用SimpleDateFormat。特别是在Servlet或控制器中:
public class MyController {
  SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");

  public void handleRequest(ServletRequest req, ServletResponse res) {
    Date date = dateFormat.parse(req.getParameter("date"));
  }
}

如果两个线程同时进入SimpleDateFormat.parse()方法,您很可能会看到ArrayIndexOutOfBoundsException异常。请注意SimpleDateFormat的类文档中的同步部分
确保在类似servlet或控制器等并发方式下,没有访问不安全的类(如SimpleDateFormat)的代码。检查您的servlet和控制器的所有实例变量,寻找可能的问题。

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