在逛 Stack Overflow 的时候,发现了一些访问量像昆仑山同样高的问题,好比说这个:为何会发生 ArrayIndexOutOfBoundsException
?这样看似简单到不值得一问的问题,访问量足足有 69万+,这不得了啊!说明有很多的初级程序员被这个问题困扰过。实话实说吧,我也有点吃不许为何。java
来回顾一下提问者的问题:程序员
ArrayIndexOutOfBoundsException
究竟意味着什么?我该如何摆脱这个错误。编程
若是你也曾被这个问题困扰过,或者正在被困扰,就请随我一块儿来梳理一下问题的答案。打怪进阶喽!数组
来看这样一段代码,它就能够引发 ArrayIndexOutOfBoundsException
。bash
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] 的时候越界了。这个问题的修正方法蛮简单的,就是把 <=
改成 <
。ui
String[] names = { "沉", "默", "王", "二" };
for (int i = 0; i < names.length; i++) {
System.out.println(names[i]);
}
复制代码
当 i
为 4 的时候要跳出 for 循环,names 的最大下标值为 3 而不是 4。spa
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
)交给智能化的编译器来处理,就是最好的办法。
好了各位读者朋友们,以上就是本文的所有内容了。能看到这里的都是人才,二哥必需要为你点个赞👍。若是以为不过瘾,还想看到更多,我再推荐几篇给你们。
Stack Overflow上188万浏览量的提问:Java 究竟是值传递仍是引用传递? Stack Overflow 上 370万浏览量的一个问题:如何比较 Java 的字符串? Stack Overflow 上 250万浏览量的一个问题:什么是 NullPointerException
养成好习惯!若是以为这篇文章有点用的话,求点赞、求关注、求分享、求留言,这将是我写下去的最强动力!