程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 體驗J2SE 1.5新特性之增強For循環

體驗J2SE 1.5新特性之增強For循環

編輯:關於JAVA

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循環,會在編譯期間被看成是這樣的形式:

清單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屬性,表示“標准輸入流”)。

的確可以通過巧妙的設計語法,讓關鍵字只在特定的上下文中有特殊的含義,來允許它們也作為普通的標識符來使用。不過這種會使語法變復雜的策略,並沒有得到廣泛的采用。

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()

把最近一次由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. 歸納總結

借助J2SE 1.5中引入的第二種for循環,可以用一種更簡單地方式來完成遍歷。能用這種方法遍歷的對象的類型,可以是數組、Collection或者任何其它實現了java.lang.Iterable接口的類。通過跟同樣是在J2SE 1.5中引入的泛型機制配合使用,可以精確的控制能采用的循環變量的類型。而且,因為這麼編寫的代碼,會在編譯期間被自動當成是和傳統寫法相同的形式,所以不必擔心要額外付出性能方面的代價。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved