J2SE 1.5提供了另一种
来遍历数组和Collection等
义能被这样遍历的类,并解
形式的for循环。借助这种形式
类型的对象。本文介绍使用这种
释和这一机制的一些常见问题。
的for循环,可以用更简单地方式
循环的具体方式,说明如何自行定
在Java程序中,要“逐
元素的时候,一般会使用一
不知道是因为for这个词的
种时候for循环比其它循环
一处理”——或者说,“遍历”
个for循环来实现(当然,用其
长度比较短,还是因为for这个
常用得多)。
——某一个数组或Collection中的
它种类的循环也不是不可以,只是
词的含义和这种操作比较配,在这
对于遍历数组,这个循环一般是采取这样的写法:
清单1:遍历数组的传统方式
/* 建立一个数组 */
int[] integers = {1, 2, 3, 4};
/* 开始遍历 */
for (int j = 0; j &l
t; integers.length; j++) {
int i = integers[j];
System.out.println(i);
}
而对于遍历Collection对象,这个循环则通常是采用这样的形式:
清单2:遍历Collection对象的传统方式
/* 建立一个Collection */
String[] strings = {"A", "B", "C
", "D"};
Collection stringList = java.uti
l.Arrays.asList(strings);
/* 开始遍历 */
for (Iterator itr =
stringList.iterator(); itr.h
asNext();) {
Object str = itr.next();
System.out.println(str);
}
而在Java语言的最新版本——J2SE 1
的for循环,现在可以用一种更简单地方
.5中,引入了另一种形式的for循环。借助这种形式
式来进行遍历的工作。
1. 第二种for循环
不严格的说,Java的第二种for循环基本是这样的格式:
for (循环变量类型 循环变量名称 : 要被遍历的对象) 循环体
借助这种语法,遍历一个数组的操作就可以采取这样的写法:
清单3:遍历数组的简单方式
/* 建立一个数组 */
int[] integers = {1, 2, 3, 4};
/* 开始遍历 */
for (int i : integers) {
System.out.print
ln(i);/* 依次输出“1”、“2
”、“3”、“4” */
}
这里所用的for循环,会在编译期间被看成是这样的形式:
清单4:遍历数组的简单方式的等价代码
/* 建立一个数组 */
int[] integers = {1, 2, 3, 4};
/* 开始遍历 */
for (int 变量名甲 =
0; 变量名甲
ength; 变量名甲++) {
System.out.print
” */
ln(integers[变量名甲]);/* 依
次输出“1”、“2”、“3”、“4
}
这里的“变量名甲”是一个由编译器自动生成的不会造成混乱的名字。
而遍历一个Collection的操作也就可以采用这样的写法:
清单5:遍历Collection的简单方式
/* 建立一个Collection */
String[] strings = {"A", "B", "C
", "D"};
Collection list = java.util.Arra
ys.asList(strings);
/* 开始遍历 */
for (Object str : list) {
System.out.print
ln(str);/* 依次输出“A”、“
B”、“C”、“D” */
}
这里所用的for循环,则会在编译期间被看成是这样的形式:
清单6:遍历Collection的简单方式的等价代码
/* 建立一个Collection */
String[] strings = {
"A", "B", "C", "D"};
Collection stringList = java.uti
l.Arrays.asList(strings);
/* 开始遍历 */
for (Iterator 变量名
乙 = list.iterator(); 变量名
乙.hasNext();) {
Object str = 变量名乙.next();
System.out.print
ln(str);/* 依次输出“A”、“
B”、“C”、“D” */
}
这里的“变量名乙”也是一个由编译器自动生成的不会造成混乱的名字。
因为在编译期间,J2SE 1.5的编译器
所以不必担心出现性能方面的问题。
会把这种形式的for循环,看成是对应的传统形式,
不用“foreach”和“in”的原因
Java采用“for”(而
for-each循环”的循环,并
要被遍历的对象。这样作的
题——在Java语言中,不允
况并不是非常多,但是“in
里,就有一个名字叫做“in
不是意义更明确的“foreach”
使用“:”(而不是意义更明确
主要原因,是为了避免因为引入
许把关键字当作变量名来使用,
”却是一个经常用来表示输入流
”的static属性,表示“标准输
)来引导这种一般被叫做“
的“in”)来分割循环变量名称和
新的关键字,造成兼容性方面的问
虽然使用“foreach”这名字的情
的名字(例如java.lang.System类
入流”)。
的确可以通过巧妙的设计语法,让关
也作为普通的标识符来使用。不过这种会
键字只在特定的上下文中有特殊的含义,来允许它们
使语法变复杂的策略,并没有得到广泛的采用。
“for-each循环”的悠久历史
“for-each循环”并不
第一个成熟的UNIX命令解释
导,循环体则用“do”和“
是一个最近才出现的控制结构。
器)里就已经包含了这种控制结
done”来标识)。
在1979正式发布的Bourne shell(
构(循环用“for”和“in”来引
2. 防止在循环体里修改循环变量
在默认情况下,编译器
过,因为这种做法对循环体
般并不推荐使用。
是允许在第二种for循环的循环
外面的情况丝毫没有影响,又容
体里,对循环变量重新赋值的。不
易造成理解代码时的困难,所以一
Java提供了一种机制,可以在编译期
类型前面加上一个“final”修饰符。这
一个编译错误。借助这一机制,就可以有
量”的操作了。
间就把这样的操作封杀。具体的方法,是在循环变量
样一来,在循环体里对循环变量进行赋值,就会导致
效的杜绝有意或无意的进行“在循环体里修改循环变
清单7:禁止重新赋值
int[] integers = {1, 2, 3, 4};
for (final int i : integers) {
i = i / 2; /* 编译时出错 */
}
注意,这只是禁止了对
环变量的内容变化的方法,
循环变量进行重新赋值。给循环
是不被禁止的。
变量的属性赋值,或者调用能让循
清单8:允许修改状态
Random[] randoms = new Random[]{
new Random(1), new Random(2), new Random(3)};
for (final Random r : randoms) {
r.setSeed(4);/* 将所有Random
对象设成使用相同的种子 */
System.out.print
ln(r.nextLong());/* 种子相同
,第一个结果也相同 */
}
3. 类型相容问题
为了保证循环变量能在
型有一定的限制。这些限制
每次循环开始的时候,都被安全
之下,循环变量的类型可以有这
的赋值,J2SE 1.5对循环变量的类
样一些选择:
循环变量的类型可以和要被遍历的对
来遍历一个int[]型的数组,用Object型
象中的元素的类型相同。例如,用int型的循环变量
的循环变量来遍历一个Collection等。
清单9:使用和要被遍历的数组中的元素相同类型的循环变量
int[] integers = {1, 2, 3, 4};
for (int i : integers) {
System.out.println(i);/* 依
次输出“1”、“2”、“3”、“4” */
}
清单10:使用和要被遍历的Collection中的元素相同类型的循环变量
Collection< String> strings = new ArrayList< String>();
strings.add("A");
strings.add("B");
strings.add("C");
strings.add("D");
for (String str : integers) {
System.out.print
ln(str);/* 依次输出“A”、“
B”、“C”、“D” */
}
循环变量的类型可以是要被遍历的对象中的元素的上级类型。例如,用int型的循环变量来遍历一个byte[]型的数组,用Object型的循环变量来遍历一个Collection< String>(全部元素都是String的Collection)等。
清单11:使用要被遍历的对象中的元素的上级类型的循环变量
String[] strings = {
"A", "B", "C", "D"};
Collection< String> list = java.util.Arrays.asList(strings);
for (Object str : list) {
System.out.print
ln(str);/* 依次输出“A”、“
B”、“C”、“D” */
}
循环变量的类型可以和要被遍历的对象中的元素的类型之间存在能自动转换的关系。J2SE 1.5中包含了“Autoboxing/Auto-Unboxing”的机制,允许编译器在必要的时候,自动在基本类型和它们的包裹类(Wrapper Classes)之间进行转换。因此,用Integer型的循环变量来遍历一个int[]型的数组,或者用byte型的循环变量来遍历一个Collection< Byte>,也是可行的。
清单12:使用能和要被遍历的对象中的元素的类型自动转换的类型的循环变量
int[] integers = {1, 2, 3, 4};
for (Integer i : integers) {
System.out.print
ln(i);/* 依次输出“1”、“2
”、“3”、“4” */
}
注意,这里说的“元素
Object[]型的数组,那么元
的类型”,是由要被遍历的对象
素的类型就是Object,即使里面
的决定的——如果它是一个
装的都是String对象也是如此。
可以限定元素类型的Collection
截至到J2SE 1.4为止,始终无法在Java程序里限定Collection中所能保存的对象的类型——它们全部被看成是最一般的Object对象。一直到J2SE 1.5中,引入了“泛型(Generics)”机制之后,这个问题才得到了解决。现在可以用Collection< T>来表示全部元素类型都是T的Collection,如Collection< String>、Collection< Integer>等。不过这里的T不能是一个简单类型,象Collection< int>之类的写法是不被认可的。
4. 被这样遍历的前提
有两种类型的对象可以通过这种方法
类的实例。试图将结果是其它类型的表达
是“foreach not applicable to expres
来遍历——数组和实现了java.lang.Iterable接口的
式放在这个位置上,只会在编译时导致一个提示信息
sion type”的问题。
java.lang.Iterable接口中定义的方法只有一个:
iterator()
返回一个实现了java.util.Iterator接口的对象
而java.util.Iterator接口中,则定义了这样三个方法:
hasNext()
返回是否还有没被访问过的对象
next()
返回下一个没被访问过的对象
remove()
把最近一次由next()返
算提供这个功能,在实现的
循环的过程中,这个方法根
回的对象从被遍历的对象里移除
时候抛出一个UnsupportedOpera
本没有机会被调用,所以是否提
。这是一个可选的操作,如果不打
tionException即可。因为在整个
供这个功能,在这里没有影响。
借助这两个接口,就可以自行实现能被这样遍历的类了。
清单13:一个能取出10个Object元素的类
import java.util.*;
class TenObjects implements Iterable {
public Iterator iterator() {
return new Iterator() {
private int count = 0;
public boolean hasNext() {
return (count
}
public Object next() {
return new Integer(count++);
}
public void remove() {
thro
w new UnsupportedOperationEx
ception();
}
};
}
public static void main(Stri
ng[] args)
{
TenObjects o
bjects = new TenObjects();
for (Object i : objects)
{
System.out.println(i
);/* 依次输出从“0"到“9”的十个整数 */
}
}
}
Collection的资格问题
在J2SE 1.5的API中,
型,看上去很象java.util.
所有能被这样遍历的对象的类型
Collection获得了编译器的特殊
都是java.util.Collection的子类
对待。
不过,造成这种现象的
java.lang.Iterable的子接
实际原因,是在J2SE 1.5中,ja
口。编译器并没有给Collection
va.util.Collection被定义成了
什么特别的关照。
从理论上说,完全可以制造出一些拒
Collection一样被用这种方法遍历。不过
不到广泛的流传。
不实现Collection接口的容器类,而且能让它们和
这样的容器类,可能会因为存在兼容性的问题,而得
若干方法的命名问题
在java.lang.Iterable
java.util.Iterator接口中
getNextElement()。造成这
这些方法往往会被频繁的调
接口中,使用iterator(),而不
,也使用hasNext()和next(),
种现象的原因,是Java Collect
用(每每还会挤到一行),所以
是getIterator();而
而不是hasNextElement()和
ions Framework的设计者们,认为
用短一点的名字更为合适。
5. 加入更精确的类型控制
如果在遍历自定义的可遍历对象的时
需要在实现java.lang.Iterable接口和ja
型机制,来作一些类型指派的工作。
候,想要循环变量能使用比Object更精确的类型,就
va.util.Iterator接口的时候,借助J2SE 1.5中的泛
如果想要使循环变量的类型为T,那么指派工作的内容是:
在所有要出现java.lang.Iterable的地方,都写成“Iterable< T>”。
在所有出现java.util.Iterator的地方,都写成“Iterator< T>”。
在实现java.util.Iterator的接口的
时候,用T作为next()方法的返回值类型。
注意,这里的T不能是
的包裹类来代替这里的T,
一个基本类型。如果打算用基本
然后借助Auto-Unboxing机制,
类型作为循环变量,那么得用它们
来近似的达到目的。
清单14:用int型的循环变量来遍历一个能取出10个Integer元素的类
import java.util.*;
public class TenIntegers implements Iterable< Integer> {
public Iterator< Integer> iterator() {
return new Iterator< Integer>() {
private int count = 0;
public boolean hasNext() {
return (count
}
public Integer next() {
return Integ
er.valueOf(count++);
}
public void remove() {
throw new Un
supportedOperationException();
}
};
}
public static void main(Stri
ng[] args)
{
TenIntegers integers = n
ew TenIntegers();
for (int i : integers)
{
System.out.println(i
);/* 依次输出从“0"到“9”的十个整数 */
}
}
}
另外,一个类只能实现一次java.lang.Iterable接口,即使在后面的尖括号里使用不同的类型。类似“class A implements Iterable< String>, Iterable< Integer>”的写法,是不能通过编译的。所以,没有办法让一个可遍历对象能在这样遍历时,既可以使用Integer,又可以使用String来作为循环变量的类型(当然,把它们换成另外两种没有继承和自动转化关系的类也一样行不通)。
6. 归纳总结
借助J2SE 1.5中引入的
方法遍历的对象的类型,可
口的类。通过跟同样是在J2
变量的类型。而且,因为这
,所以不必担心要额外付出
第二种for循环,可以用一种更
以是数组、Collection或者任何
SE 1.5中引入的泛型机制配合使
么编写的代码,会在编译期间被
性能方面的代价。
posted on 2006-09-24 15:27 77 阅读(159) 评论(0) 编辑 收藏