本文很是適合初學Java的程序員,主要是來了解一下Java中的幾種for循環用法,分析得十分詳細,一塊兒來看看。java
J2SE 1.5提供了另外一種形式的for循環。藉助這種形式的for循環,能夠用更簡單地方式來遍歷數組和Collection等類型的對象。本文介紹使用這種循環的具體方式,說明如何自行定義能被這樣遍歷的類,並解釋和這一機制的一些常見問題。程序員
在Java程序中,要「逐一處理」――或者說,「遍歷」――某一個數組或Collection中的元素的時候,通常會使用一個for循環來實現(當 然,用其它種類的循環也不是不能夠,只是不知道是由於for這個詞的長度比較短,仍是由於for這個詞的含義和這種操做比較配,在這種時候for循環比其 它循環經常使用得多)。shell
對於遍歷數組,這個循環通常是採起這樣的寫法:數組
清單1:遍歷數組的傳統方式安全
/* 創建一個數組 */ int[] integers = {1, 2, 3, 4}; /* 開始遍歷 */ for (int j = 0; j < integers.length; j++) { int i = integers[j]; System.out.println(i); }
而對於遍歷Collection對象,這個循環則一般是採用這樣的形式:app
清單2:遍歷Collection對象的傳統方式dom
/* 創建一個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循環,如今能夠用一種更簡單地方式來進行遍歷的工做。性能
不嚴格的說,Java的第二種for循環基本是這樣的格式:spa
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的簡單方式
/* 創建一個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();) { Object str = 變量名乙.next(); System.out.println(str); /* 依次輸出「A」、「B」、「C」、「D」 */ }
這裏的「變量名乙」也是一個由編譯器自動生成的不會形成混亂的名字。
由於在編譯期間,J2SE 1.5的編譯器會把這種形式的for循環,當作是對應的傳統形式,因此沒必要擔憂出現性能方面的問題。
不用「foreach」和「in」的緣由
Java採用「for」(而不是意義更明確的「foreach」)來引導這種通常被叫作「for-each循環」的循環,並使用「:」(而不是意義 更明確的「in」)來分割循環變量名稱和要被遍歷的對象。這樣做的主要緣由,是爲了不由於引入新的關鍵字,形成兼容性方面的問題――在Java語言中, 不容許把關鍵字看成變量名來使用,雖然使用「foreach」這名字的狀況並非很是多,可是「in」倒是一個常常用來表示輸入流的名字(例如 java.lang.System類裏,就有一個名字叫作「in」的static屬性,表示「標準輸入流」)。
的確能夠經過巧妙的設計語法,讓關鍵字只在特定的上下文中有特殊的含義,來容許它們也做爲普通的標識符來使用。不過這種會使語法變複雜的策略,並無獲得普遍的採用。
「for-each循環」的悠久歷史
「for-each循環」並非一個最近纔出現的控制結構。在1979正式發佈的Bourne shell(第一個成熟的UNIX命令解釋器)裏就已經包含了這種控制結構(循環用「for」和「in」來引導,循環體則用「do」和「done」來標識)。
在默認狀況下,編譯器是容許在第二種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」 */ }
清單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對象也是如此。
能夠限定元素類型的Collection
截至到J2SE 1.4爲止,始終沒法在Java程序裏限定Collection中所能保存的對象的類型――它們所有被當作是最通常的Object對象。一直到J2SE 1.5中,引入了「泛型(Generics)」機制以後,這個問題才獲得瞭解決。如今能夠用Collection< T>來表示所有元素類型都是T的Collection。