体验J2SE 1.5新特性之加强For轮回
副标题#e#
J2SE 1.5提供了另一种形式的for轮回。借助这种形式的for轮回,可以用更简朴处所式来遍历数组和Collection等范例的工具。本文先容利用这种轮回的详细方法,说明如何自行界说能被这样遍历的类,并表明和这一机制的一些常见问题。
在Java措施中,要“逐一处理惩罚”——可能说,“遍历”——某一个数组或Collection中的元素的时候,一般会利用一个for轮回来实现(虽然,用其它种类的轮回也不是不行以,只是不知道是因为for这个词的长度较量短,照旧因为for这个词的寄义和这种操纵较量配,在这种时候for轮回比其它轮回常用得多)。
对付遍历数组,这个轮回一般是采纳这样的写法:
清单1:遍历数组的传统方法
/* 成立一个数组 */
int[] integers = {1, 2, 3, 4};
/* 开始遍历 */
for (int j = 0; j < integers.length; j++) {
int i = integers[j];
System.out.println(i);
}
而对付遍历Collection工具,这个轮回则凡是是回收这样的形式:
清单2:遍历Collection工具的传统方法
/* 成立一个Collection */
String[] strings = {"A", "B", "C", "D"};
Collection stringList = java.util.Arrays.asList(strings);
/* 开始遍历 */
for (Iterator itr = stringList.iterator(); itr.hasNext();) {
Object str = itr.next();
System.out.println(str);
}
而在Java语言的最新版本——J2SE 1.5中,引入了另一种形式的for轮回。借助这种形式的for轮回,此刻可以用一种更简朴处所式来举办遍历的事情。
1. 第二种for轮回
不严格的说,Java的第二种for轮回根基是这样的名目:
for (轮回变量范例 轮回变量名称 : 要被遍历的工具) 轮回体
借助这种语法,遍历一个数组的操纵就可以采纳这样的写法:
清单3:遍历数组的简朴方法
/* 成立一个数组 */
int[] integers = {1, 2, 3, 4};
/* 开始遍历 */
for (int i : integers) {
System.out.println(i);/* 依次输出“1”、“2”、“3”、“4” */
}
这里所用的for轮回,会在编译期间被当作是这样的形式:
#p#副标题#e#
清单4:遍历数组的简朴方法的等价代码
/* 成立一个数组 */
int[] integers = {1, 2, 3, 4};
/* 开始遍历 */
for (int 变量名甲 = 0; 变量名甲 < integers.length; 变量名甲++) {
System.out.println(变量名甲);/* 依次输出“1”、“2”、“3”、“4” */
}
这里的“变量名甲”是一个由编译器自动生成的不会造成杂乱的名字。
而遍历一个Collection的操纵也就可以回收这样的写法:
清单5:遍历Collection的简朴方法
/* 成立一个Collection */
String[] strings = {"A", "B", "C", "D"};
Collection list = java.util.Arrays.asList(strings);
/* 开始遍历 */
for (Object str : list) {
System.out.println(str);/* 依次输出“A”、“B”、“C”、“D” */
}
这里所用的for轮回,则会在编译期间被当作是这样的形式:
清单6:遍历Collection的简朴方法的等价代码
/* 成立一个Collection */
String[] strings = {"A", "B", "C", "D"};
Collection stringList = java.util.Arrays.asList(strings);
/* 开始遍历 */
for (Iterator 变量名乙 = list.iterator(); 变量名乙.hasNext();) {
System.out.println(变量名乙.next());/* 依次输出“A”、“B”、“C”、“D” */
}
这里的“变量名乙”也是一个由编译器自动生成的不会造成杂乱的名字。
因为在编译期间,J2SE 1.5的编译器会把这种形式的for轮回,当作是对应的传统形式,所以不必担忧呈现机能方面的问题。
不消“foreach”和“in”的原因
Java回收“for”(而不是意义更明晰的“foreach”)来引导这种一般被叫做“for-each轮回”的轮回,并利用“:”(而不是意义更明晰的“in”)来支解轮回变量名称和要被遍历的工具。这样作的主要原因,是为了制止因为引入新的要害字,造成兼容性方面的问题——在Java语言中,不答允把要害字看成变量名来利用,固然利用“foreach”这名字的环境并不长短常多,可是“in”却是一个常常用来暗示输入流的名字(譬喻java.lang.System类里,就有一个名字叫做“in”的static属性,暗示“尺度输入流”)。
#p#分页标题#e#
简直可以通过巧妙的设计语法,让要害字只在特定的上下文中有非凡的寄义,来答允它们也作为普通的标识符来利用。不外这种会使语法变巨大的计策,并没有获得遍及的回收。
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.println(r.nextLong());/* 种子沟通,第一个功效也沟通 */
}
3. 范例相容问题
为了担保轮回变量能在每次轮回开始的时候,都被安详的赋值,J2SE 1.5对轮回变量的范例有必然的限制。这些限制之下,轮回变量的范例可以有这样一些选择:
轮回变量的范例可以和要被遍历的工具中的元素的范例沟通。譬喻,用int型的轮回变量来遍历一个int[]型的数组,用Object型的轮回变量来遍历一个Collection等。
清单9:利用和要被遍历的工具中的元素沟通范例的轮回变量
int[] integers = {1, 2, 3, 4};
for (int i : integers) {
System.out.println(i);/* 依次输出“1”、“2”、“3”、“4” */
}
轮回变量的范例可以是要被遍历的工具中的元素的上级范例。譬喻,用int型的轮回变量来遍历一个byte[]型的数组,用Object型的轮回变量来遍历一个Collection<String>(全部元素都是String的Collection)等。
清单10:利用要被遍历的工具中的元素的上级范例的轮回变量
String[] strings = {"A", "B", "C", "D"};
Collection<String> list = java.util.Arrays.asList(strings);
for (Object str : list) {
System.out.println(str);/* 依次输出“A”、“B”、“C”、“D” */
}
轮回变量的范例可以和要被遍历的工具中的元素的范例之间存在能自动转换的干系。J2SE 1.5中包括了“Autoboxing/Auto-Unboxing”的机制,答允编译器在须要的时候,自动在根基范例和它们的包裹类(Wrapper Classes)之间举办转换。因此,用Integer型的轮回变量来遍历一个int[]型的数组,可能用byte型的轮回变量来遍历一个Collection<Byte>,也是可行的。
清单11:利用能和要被遍历的工具中的元素的范例自动转换的范例的轮回变量
int[] integers = {1, 2, 3, 4};
for (Integer i : integers) {
System.out.println(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. 被这样遍历的前提
有两种范例的工具可以通过这种要领来遍历——数组和实现了java.lang.Iterable接口的类的实例。试图将功效是其它范例的表达式放在这个位置上,只会在编译时导致一个提示信息是“foreach not applicable to expression type”的问题。
java.lang.Iterable接口中界说的要领只有一个:
iterator()
返回一个实现了java.util.Iterator接口的工具
而java.util.Iterator接口中,则界说了这样三个要领:
hasNext()
返回是否尚有没被会见过的工具
next()
返回下一个没被会见过的工具
remove()
#p#分页标题#e#
把最近一次由next()返回的工具从被遍历的工具里移除。这是一个可选的操纵,假如不规划提供这个成果,在实现的时候抛出一个UnsupportedOperationException即可。因为在整个轮回的进程中,这个要领基础没有时机被挪用,所以是否提供这个成果,在这里没有影响。
借助这两个接口,就可以自行实现能被这样遍历的类了。—www.bianceng.cn。
清单12:一个能取出10个Object元素的类
import java.util.*;
class TenObjects implements Iterable {
public Iterator iterator() {
return new Iterator() {
private int count = 0;
public boolean hasNext() {
return (count < 10);
}
public Object next() {
return new Integer(count++);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args)
{
TenObjects objects = new TenObjects();
for (Object i : objects)
{
System.out.println(i);/* 依次输出从“0"到“9”的十个整数 */
}
}
}
Collection的资格问题
在J2SE 1.5的API中,所有能被这样遍历的工具的范例都是java.util.Collection的子范例,看上去很象java.util.Collection得到了编译器的非凡看待。
不外,造成这种现象的实际原因,是在J2SE 1.5中,java.util.Collection被界说成了java.lang.Iterable的子接口。编译器并没有给Collection什么出格的看护。
从理论上说,完全可以制造出一些拒不实现Collection接口的容器类,并且能让它们和Collection一样被用这种要领遍历。不外这样的容器类,大概会因为存在兼容性的问题,而得不到遍及的传播。
若干要领的定名问题
在java.lang.Iterable接口中,利用iterator(),而不是getIterator();而java.util.Iterator接口中,也利用hasNext()和next(),而不是hasNextElement()和getNextElement()。造成这种现象的原因,是Java Collections Framework的设计者们,认为这些要领往往会被频繁的挪用(通常还会挤到一行),所以用短一点的名字更为符合。
5. 插手更准确的范例节制
假如在遍历自界说的可遍历工具的时候,想要轮回变量能利用比Object更准确的范例,就需要在实现java.lang.Iterable接口和java.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机制,来近似的到达目标。
清单13:用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 < 10);
}
public Integer next() {
return Integer.valueOf(count++);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args)
{
TenIntegers integers = new TenIntegers();
for (int i : integers)
{
System.out.println(i);/* 依次输出从“0"到“9”的十个整数 */
}
}
}
别的,一个类只能实现一次java.lang.Iterable接口,纵然在后头的尖括号里利用差异的范例。雷同“class A implements Iterable<String>, Iterable<Integer>”的写法,是不能通过编译的。所以,没有步伐让一个可遍历工具能在这样遍历时,既可以利用Integer,又可以利用String来作为轮回变量的范例(虽然,把它们换成别的两种没有担任和自动转化干系的类也一样行不通)。
6. 归纳总结
#p#分页标题#e#
借助J2SE 1.5中引入的第二种for轮回,可以用一种更简朴处所式来完成遍历。能用这种要领遍历的工具的范例,可以是数组、Collection可能任何其它实现了java.lang.Iterable接口的类。通过跟同样是在J2SE 1.5中引入的泛型机制共同利用,可以准确的节制能回收的轮回变量的范例。并且,因为这么编写的代码,会在编译期间被自动当成是和传统写法沟通的形式,所以不必担忧要特别支付机能方面的价钱。