J2EE 1.5 提供的另一種形式的for循環
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; 變量名甲 < integers.l |
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 < 10);???????????????? |
}???????????????????????????????????????????????????????????????? |
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 < 10);???????????? |
}???????????????????????????????????????????????????????????? |
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中引入的泛型機制配合使 么編寫的代碼,會在編譯期間被 性能方面的代價。 |