1. 程式人生 > >Stack Overflow上59萬瀏覽量的提問:為什麼會發生ArrayIndexOutOfBoundsException?

Stack Overflow上59萬瀏覽量的提問:為什麼會發生ArrayIndexOutOfBoundsException?

在逛 Stack Overflow 的時候,發現了一些訪問量像崑崙山一樣高的問題,比如說這個:為什麼會發生 ArrayIndexOutOfBoundsException?這樣看似簡單到不值得一問的問題,訪問量足足有 69萬+,這不得了啊!說明有不少的初級程式設計師被這個問題困擾過。實話實說吧,我也有點吃不準為什麼。

來回顧一下提問者的問題:

ArrayIndexOutOfBoundsException 究竟意味著什麼?我該如何擺脫這個錯誤。

如果你也曾被這個問題困擾過,或者正在被困擾,就請隨我一起來梳理一下問題的答案。打怪進階嘍!

來看這樣一段程式碼,它就可以引起 ArrayIndexOutOfBoundsException

String[] names = { "沉", "默", "王", "二" };
for (int i = 0; i <= names.length; i++) {
    System.out.println(names[i]);
}

錯誤的堆疊資訊如下所示。

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
    at com.cmower.java_demo.stackoverflow.Cmower1.main(Cmower1.java:7)

丟擲這個錯誤的原因是由於陣列使用了非法的下標訪問,比如說下標為負數或者大於或者等於陣列的長度。

因為陣列 names 的長度為 4,但下標的起始位置為 0,而不是 1,導致 names[4] 的時候越界了。這個問題的修正方法蠻簡單的,就是把 <= 改為 <

String[] names = { "沉", "默", "王", "二" };
for (int i = 0; i < names.length; i++) {
    System.out.println(names[i]);
}

i 為 4 的時候要跳出 for 迴圈,names 的最大下標值為 3 而不是 4。

Java 的下標都是從 0 開始編號的(我不確定有沒有從 1 開始的程式語言),這和我們平常生活中從 1 開始編號的習慣不同。Java 這樣做的原因如下:

Java 是基於 C 語言實現的,而 C 語言的下標是從 0 開始的——這聽起來好像是一句廢話。真正的原因是下標並不是下標,在指標(C)語言中,它實際上是一個偏移量,距離開始位置的一個偏移量。第一個元素在開頭,因此它的偏移量就為 0。

此外,還有另外一種說法。早期的計算機資源比較匱乏,0 作為起始下標相比較於 1 作為起始下標,編譯的效率更高。

比如說,10 個元素的陣列其結構如下圖所示。編號從 0 開始,第 9 個元素將在下標 8 處訪問。

為了擺脫 ArrayIndexOutOfBoundsException 的困擾,除了 i < 0; i < names.length;還有一種更值得推薦的做法——使用增強的 for 迴圈,當我們確定不需要使用下標的時候。

String[] names = { "沉", "默", "王", "二" };
for (String name : names) {
    System.out.println(name);
}

增強的 for 迴圈,徹底地甩掉了使用陣列下標的可能性,也就徹底地擺脫了 ArrayIndexOutOfBoundsException。雖然這只是針對我們開發者來說。

實際上,Java 會把增強的 for 迴圈語句解釋為普通的 for 迴圈語句,仍然會使用下標。

String[] names = new String[]{"沉", "默", "王", "二"};
String[] var2 = names;
int var3 = names.length;

for(int var4 = 0; var4 < var3; ++var4) {
    String name = var2[var4];
    System.out.println(name);
}

下標 var4 的起始值為 0,var3 為陣列的長度;當 var4 自增長為 4 的時候,發現 var4 不小於 var3,於是迴圈退出。

但不管怎麼說,增強的 for 迴圈的確為我們開發者帶來了福音——有效地擺脫了 ArrayIndexOutOfBoundsException

來對比一下普通的 for 迴圈和反編譯後的增強 for 迴圈,看看它們之間有什麼區別。

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

int var3 = names.length;
for(int var4 = 0; var4 < var3; ++var4) {
    String name = var2[var4];
    System.out.println(name);
}

從效能的角度來看,差別主要有兩點。

1)增強的 for 迴圈在遍歷之前獲取了陣列的長度,並儲存到了一個臨時變數 var3 中,這就避免了每次迴圈的時候再去獲取一次陣列長度。

2)增強的 for 迴圈使用了前置自增 ++var4,而普通的 for 迴圈使用了後置自增 i++。這兩者之間是有一定的差別的,感興趣的同學可以瞭解一下。

如果使用的是 JDK8 以上的版本,我們還可以這樣遍歷陣列(不使用下標)。

第一種:使用 List.forEach

Arrays.asList(names).forEach(System.out::println);

第二種:使用 Stream

Stream.of(names).forEach(System.out::println);

如果需要對陣列執行其他操作,比如說過濾等操作,可以將陣列轉換為“流”。

這兩種做法都需要用到 forEach() 方法,該方法其實是通過增強的 for 迴圈實現的,原始碼如下所示。

public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    for (E e : a) {
        action.accept(e);
    }
}

說到底,如果想要擺脫 ArrayIndexOutOfBoundsException 的困擾,使用增強的 for 迴圈來遍歷陣列就對了。把我們開發者容易疏忽的錯誤(比如 i <= names.length)交給智慧化的編譯器來處理,就是最好的辦法。


好了各位讀者朋友們,以上就是本文的全部內容了。能看到這裡的都是人才,二哥必須要為你點個贊