For-Each循环
Posted Deolin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了For-Each循环相关的知识,希望对你有一定的参考价值。
For-Each是Java中For-Index的一种加强,是Java 5带来的新语法糖。
什么场合应该使用它?
For-Each似乎并不是必须的,但很多场合下使用它十分合适。
在实际开发中,经常会出现需要遍历数组,或是Collection容器的情况,就像source1那样。
1 /**
2 * source1
3 */
4 String[] args = {"a", "b", "c"};5 for (int i = 0; i < args.length; i++) {
6 String arg = args[i];
7 System.out.println(arg);
8 }
source1中的for循环仅仅希望得到args数组中的每一个元素,但是看看为此做了多少额外的事情,
第一件事。在循环了声明了下标i。真正归 For-Index 负责的任务其实是在满足要求的情况下不停地i++,所以在{}代码块中定义的代码块更像是一种附带——由于i在不断自增,那么在代码块中“顺便”可以取到args中的每一个值。
第二件事。For-Index 只关注int i。所以想要取得每一个元素,还得声明一个arg变量来引用每一个元素(虽然source1中,arg只被用到了一次,但实际开发中常常不只如此,根据DONT REPEAT YOURSELF原则,arg是必要的)。
而使用For-Each,在编码上解决了这两个问题。
1 /**
2 * source2
3 */
4 String[] args = {"a", "b", "c"};
5 for (String arg : args) {
6 System.out.println(arg);
7 }
For-Each直接()中声明了arg引用,不需要在代码块中专门声明。int i也不再必要了,For-Each会循环到args中无值可取为止。
显然,单纯为了遍历数组或容器对象中的每个元素,For-Each比For-Index在编码上更合适。在可读性方法,For-Each很容易让人知道设计者希望遍历冒号后面对象的全部元素。
Deolin在一开始并不习惯使用For-Each,直到忍受不了符合格式规约的For-Index需要在()中写大量的空格。
哪些类型的对象可以适用For-Each?
- 数组
- Collection类
- 任何实现了Iterable接口的自定义类
(根据面向接口的思想,Deolin习惯把第三类对象称之为“可迭代的”对象)
第一类,第二类在实际开发中经常用到,而第三类能够适用For-Each的原因需要通过源码来进行分析。
For-Each的原理是什么?
首先,尝试着把For-Each的对象改成一个String,
1 /** 2 * source3 3 */ 4 String oneArg = "a"; 5 for (String arg : oneArg) { }
发生了一个编译错误——Can only iterate over an array or an instance of java.lang.Iterable,
For-Each无法遍历数组和“可遍历的”实例以外的对象。
那么调查一下java.lang.Iterable的source,这个接口只有一个方法声明——Iterator<T> iterator()(Java 8之后又追加了两个default方法),
也就是说,适用For-Each的对象实际上都实现了Iterator<T> iterator()方法。
For-Each是通过调用Iterator obj的iterator()方法获得一个“迭代子”(Iterator对象)来实现迭代的。
即,身为了一个“可迭代的”对象,必须实现“返回一个迭代子”的方法,让外部能借此来迭代自己。
For-Each取得Iterator对象之后,会反复调用hasNext()和next()方法,原理与while类似
1 /** 2 * source4 3 */ 4 List<String> list = new ArrayList<String>(); 5 list.add("a"); 6 list.add("b"); 7 list.add("c"); 8 9 Iterator iterator = list.iterator(); 10 while (iterator.hasNext()) { 11 String s = (String) iterator.next(); 12 System.out.println(s); 13 }
在java.util.ArrayList$Itr的hasNext()和next()中添加两个断点可以印证这点。
利用For-Each的原理定制化迭代策略
现在定义一个List(source5),如果想要实现倒序输出,通过代理模式定制一个反迭代器似乎是个不错的方法。
1 /** 2 * source5 3 */ 4 List<String> list = new ArrayList<String>(); 5 list.add("a"); 6 list.add("b"); 7 list.add("c"); 8 list.add("d");
首先,定义一个代理类,使其能够持有一个Collection对象,并代理它的行为。
并且在定义一个向For-Each提供Iterable对象的对外方法。
1 /** 2 * source6 3 */ 4 class ReversibleList<T> { 5 // 持有一个List 6 private List<T> list; 7 8 public ReversibleList(List<T> list) { 9 this.list = list; 10 } 11 12 public int size() { 13 return list.size(); 14 } 15 16 public T get(int i) { 17 return list.get(i); 18 } 19 20 public Iterable<T> getIterableObj() { 21 return new Iterable<T>() { 22 23 @Override 24 public Iterator<T> iterator() { 25 // 定制的迭代器 26 return new Iterator<T>() { 27 28 private int index = size() - 1; 29 30 @Override 31 public boolean hasNext() { 32 return index > -1; 33 } 34 35 @Override 36 public T next() { 37 return get(index --); 38 } 39 40 }; 41 } 42 43 }; 44 }
测试一下
1 /** 2 * source7 3 */ 4 public static void main(String[] args) { 5 List<String> list = new ArrayList<String>(); 6 list.add("a"); 7 list.add("b"); 8 list.add("c"); 9 list.add("d"); 10 ReversibleList<String> rl = new ReversibleList<String>(list); 11 for(String s : rl.getIterableObj()) { 12 System.out.println(s); 13 } 14 }
输出结果
d
c
b
a
以上是关于For-Each循环的主要内容,如果未能解决你的问题,请参考以下文章
For-Each循环Java错误ArrayIndexOutOfBoundsException
淘汰 JS $parent 对象在 for-each 循环中更改其信息