在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對象的傳統方式
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循環基本是這樣的格式:
借助這種語法,遍歷一個數組的操作就可以采取這樣的寫法:
清單3:遍歷數組的簡單方式
int[] integers = {1, 2, 3, 4};
/* 開始遍歷 */
for (int i : integers) {
System.out.println(i);/* 依次輸出“1”、“2”、“3”、“4” */
}
這里所用的for循環,會在編譯期間被看成是這樣的形式:
清單4:遍歷數組的簡單方式的等價代碼
int[] integers = {1, 2, 3, 4};
/* 開始遍歷 */
for (int 變量名甲 = 0; 變量名甲 < integers.length; 變量名甲++) {
System.out.println(integers[變量名甲]);/* 依次輸出“1”、“2”、“3”、“4” */
}
這里的“變量名甲”是一個由編譯器自動生成的不會造成混亂的名字。
而遍歷一個Collection的操作也就可以采用這樣的寫法:
清單5:遍歷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的簡單方式的等價代碼
String[] strings = {"A", "B", "C", "D"};
Collection stringList = java.util.Arrays.asList(strings);
/* 開始遍歷 */
for (Iterator 變量名乙 = list.iterator(); 變量名乙.hasNext();) {
Object str = 變量名乙.next();
System.out.println(str);/* 依次輸出“A”、“B”、“C”、“D” */
}
這里的“變量名乙”也是一個由編譯器自動生成的不會造成混亂的名字。
因為在編譯期間,J2SE 1.5的編譯器會把這種形式的for循環,看成是對應的傳統形式,所以不必擔心出現性能方面的問題。
2. 防止在循環體里修改循環變量
在默認情況下,編譯器是允許在第二種for循環的循環體里,對循環變量重新賦值的。不過,因為這種做法對循環體外面的情況絲毫沒有影響,又容易造成理解代碼時的困難,所以一般并不推薦使用。
Java提供了一種機制,可以在編譯期間就把這樣的操作封殺。具體的方法,是在循環變量類型前面加上一個“final”修飾符。這樣一來,在循環體里對循環變量進行賦值,就會導致一個編譯錯誤。借助這一機制,就可以有效的杜絕有意或無意的進行“在循環體里修改循環變量”的操作了。
清單7:禁止重新賦值
for (final int i : integers) {
i = i / 2; /* 編譯時出錯 */
}
注意,這只是禁止了對循環變量進行重新賦值。給循環變量的屬性賦值,或者調用能讓循環變量的內容變化的方法,是不被禁止的。
清單8:允許修改狀態
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” */
}清單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.println(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.println(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.println(i);/* 依次輸出“1”、“2”、“3”、“4” */
}
注意,這里說的“元素的類型”,是由要被遍歷的對象的決定的——如果它是一個Object[]型的數組,那么元素的類型就是Object,即使里面裝的都是String對象也是如此。
4. 被這樣遍歷的前提
有兩種類型的對象可以通過這種方法來遍歷——數組和實現了java.lang.Iterable接口的類的實例。試圖將結果是其它類型的表達式放在這個位置上,只會在編譯時導致一個提示信息是“foreach not applicable to expression type”的問題。
java.lang.Iterable接口中定義的方法只有一個:
- iterator()
- 返回一個實現了java.util.Iterator接口的對象
而java.util.Iterator接口中,則定義了這樣三個方法:
- hasNext()
- 返回是否還有沒被訪問過的對象
- next()
- 返回下一個沒被訪問過的對象
- remove()
- 把最近一次由next()返回的對象從被遍歷的對象里移除。這是一個可選的操作,如果不打算提供這個功能,在實現的時候拋出一個UnsupportedOperationException即可。因為在整個循環的過程中,這個方法根本沒有機會被調用,所以是否提供這個功能,在這里沒有影響。
借助這兩個接口,就可以自行實現能被這樣遍歷的類了。
清單13:一個能取出10個Object元素的類
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”的十個整數 */
}
}
}
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機制,來近似的達到目的。
清單14:用int型的循環變量來遍歷一個能取出10個Integer元素的類
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. 歸納總結
借助J2SE 1.5中引入的第二種for循環,可以用一種更簡單地方式來完成遍歷。能用這種方法遍歷的對象的類型,可以是數組、Collection或者任何其它實現了java.lang.Iterable接口的類。通過跟同樣是在J2SE 1.5中引入的泛型機制配合使用,可以精確的控制能采用的循環變量的類型。而且,因為這么編寫的代碼,會在編譯期間被自動當成是和傳統寫法相同的形式,所以不必擔心要額外付出性能方面的代價。
文章來源于領測軟件測試網 http://www.kjueaiud.com/