在java 1.5 版本之前,我们对集合的遍历,首选用迭代器
List c = new ArrayList<>();
c.add("a");
for(Iterator i = c.iterator(); i.hasNext(); ){
String s = (String) i.next();
System.out.println(s);
}
遍历数组,用for循环
for(int i = 0; i< a.length; i++){
System.out.println(a[i]);
}
经过上一条,防止 a.length 多次调用,可以优化,改为
for(int i = 0, count = a.length; i< count; i++){
System.out.println(a[i]);
}
上述用for比用while效果要好,但并不是很完美。迭代器和索引会造成某些混乱,或者一不留神,拷贝错误,把一些参数复制错了,就会造成误伤,幸运的是,增强for在java 1.5 时出现了,隐藏了迭代器和索引,适用于 集合 数组 ,上述方法可修改为
List<String> c = new ArrayList<>();
for(String s : c){
System.out.println(s);
}
for(int i : a){
System.out.println(a[i]);
}
我们发现,for-each也就是增强for,里面没有迭代器与索引,只有对用的对象,这个可以理解是一个封装,对索引和集合的个数一次计算清楚,我们直接调用即可。对于两个集合的遍历,按照数中的例子,把枚举装到集合里,每个集合对应的就是枚举的值
public enum Suit {
CLUB,DIAMOND,HEART,SPARE
}
public enum Rank {
ACE,DEUCE,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE,TEN,JACK,QUEEN,KING
}
void test1(){
Collection<Suit> suit = Arrays.asList(Suit.values());
Collection<Rank> rank = Arrays.asList(Rank.values());
for (Iterator<Suit> i = suit.iterator(); i.hasNext(); ) {
for (Iterator<Rank> j = rank.iterator(); j.hasNext(); ) {
System.out.println(i.next() + "+" + j.next());
}
}
}
打印结果,估计有点出人意料
CLUB+ACE
DIAMOND+DEUCE
HEART+THREE
SPARE+FOUR
Exception in thread "main" java.util.NoSuchElementException
at java.util.AbstractList$Itr.next(AbstractList.java:364)
为什么会这样呢,我们一行行来解读代码,有两个枚举类型,Suit有四种类型,Rank有十三中类型,他们都取了values()值,转换为集合,也就是说集合 suit 元素个数是4个,集合 rank 元素个数是13个,下面是两个for循环嵌套,重点来了,看第一个for循环,集合 suit 获取了迭代器,判断是否有下一个元素,此时假设索引为a,索引a在第一个元素前面,i.hasNext() 判断索引a如果往后移,后面是否有元素,此时,肯定是有的,所以进入里面的for循环,同理,集合rank 也获取了迭代器,假设它的索引为b,j.hasNext() 也判断索引b如果后移,是否还有元素,此时也有,重点来了,看里面的代码 System.out.println(i.next() + "+" + j.next()); 这是个打印日志,但问题是 i.next() 和 j.next(), 也就是说打印元素的同时,索引a 和 索引b 都往后移动了一位,然后继续是里层的for循环,此时 j.hasNext() 仍满足条件,应为 rank 有13个元素,这时候索引b走到第一个元素之后,第二个元素之前,然后再次执行System.out.println(i.next() + "+" + j.next()); 我们发现,打印了两次,i.next() 执行了两次,对应的索引a 往后移动了两位,j.next() 对应的索引b 也往后移动了两位,然后重复上面步骤。也就是说,没打印一次,两个迭代器的索引都往后移动了,由于 外层元素为4个,里层元素13个,里层大于外层,所以在第五次打印时,i.next() 已经数组越界了,因为没有值了,此时外层for循环只执行了一次,内层for循环执行了五次,第五次报错,因为内层for循环判断的条件是 j.hasNext() ,也就是索引b 没有越界,没有判断索引a 的越界,所以报错了。即使不报错,我们是要遍历所有的组合情况,这个逻辑写法也不正确。可以修改为
void test2(){
Collection<Suit> suit = Arrays.asList(Suit.values());
Collection<Rank> rank = Arrays.asList(Rank.values());
for (Iterator<Suit> i = suit.iterator(); i.hasNext(); ) {
Suit suitValue = i.next();
for (Iterator<Rank> j = rank.iterator(); j.hasNext(); ) {
System.out.println(suitValue + "+" + j.next());
}
}
}
把 i.next(); 也就是索引a 的后移,放在内层for循环之前,这样,每层for循环控制好自己的索引,就可以了。 但是,如果用了增强for,那么就简单多了
void test3(){
Collection<Suit> suit = Arrays.asList(Suit.values());
Collection<Rank> rank = Arrays.asList(Rank.values());
for (Suit su : suit) {
for (Rank ra : rank) {
System.out.println(su + "+" + ra);
}
}
}
简单明了,并且结果正确。
增强for可以遍历数组和集合,也能遍历实现Iterable接口的对象,我们编写类时,实现了这个接口,就可以让调用者使用增强for了,大多数情况,增强for比较好用,有几种情况特殊,无法使用,比如遍历集合,删除指定的元素,增强for循环允许调用一侧remove()方法,但必须马上break或return跳出循环或终止循环,超过一个remove()就会报错,所以删除元素最好还是用传统for循环;遍历数组,并替换某些元素,也需要用传统for;并行遍历多个集合,要控制索引变量,这时候就需要传统for循环了。