越短越好
这是硬核计算机程序员的最基本公理之一:因为short等于更少的键入 ,所以short必须本质上更好。 这种理念催生了像vi这样的IDE,其中的命令如:wq!
28G
具有丰富的意义。 它还导致了您遇到过的一些最神秘的代码-诸如ar
for Runner的ar
变量(或者是Argyle还是Atomic Reactor ...好了,您就明白了)。
有时,为了简短起见,程序员将清晰度留在了后视镜中。 就是说,对空头的强烈反对导致代码过于冗长而令人痛苦。 名为theAtomicReactorLocatedInPhiladelphia
变量与名为ar
变量一样烦人且笨拙。 必须有一个快乐的媒介,对不对?
至少就我而言,这种快乐的媒介植根于寻找便捷的做事方式,而这种方式并不是为了简短而短暂。 作为此类解决方案的一个很好的例子,Java 5.0引入了for
循环的新版本,我将其称为for/in
。 它也被称为的foreach,有时也增强了 -但这些都是指同一构造。 无论您选择调用哪种方式, for/in
都可以简化代码,如您在本文示例中所看到的。
失去迭代器
与for/in
,使用for/in
的最根本区别是您不必使用计数器(通常称为i
或count
)或Iterator
。 请查看清单1,该清单显示了使用Iterator
的for
循环:
清单1. for循环,老式风格
public void testForLoop(PrintStream out) throws IOException {
List list = getList(); // initialize this list elsewhere
for (Iterator i = list.iterator(); i.hasNext(); ) {
Object listElement = i.next();
out.println(listElement.toString());
// Do something else with this list element
}
}
注意:如果您一直在关注有关Tiger的新功能的文章(请参阅参考资料 ),您会知道我经常感谢O'Reilly Media,Inc.,它使我能够从本文的Tiger书籍中发布代码示例。格式。 这意味着您所获得的代码经过了更多的测试和更多的注释,而我无法提供给您。
如果您期望对如何将此代码转换为新的for/in
循环进行冗长的描述,我将使您感到失望。 清单2显示了清单1中使用for/in
的循环,它非常相似。 看一看,我将尽我所能详细解释正在发生的事情(仍然只需要一段段落)。
清单2.转换为for / in
public void testForInLoop(PrintStream out) throws IOException {
List list = getList(); // initialize this list elsewhere
for (Object listElement : list) {
out.println(listElement.toString());
// Do something else with this list element
}
}
清单3中显示了for/in
循环的基本语法。如果您不习惯阅读规范,这可能看起来有些奇怪,但是当您逐步阅读它时,这很容易。
清单3.基本的for / in循环结构
for (declaration : expression)
statement
声明只是一个变量,例如Object listElement
。 应该键入此变量,以使其与要迭代的列表,数组或集合中的每个项目兼容。 对于清单2, list
包含对象,因此就是listElement
的类型。
表达是……很好……表达。 它应该评估可以迭代的内容(更多有关这意味着什么)。 现在,仅需确保表达式的计算结果为集合或数组即可。 该表达式可以像变量(如清单2所示)或方法调用(如getList()
)那样简单,也可以像涉及布尔逻辑或三元运算符的复杂表达式一样getList()
。 只要它返回数组或集合,就可以正常工作。
语句是循环内容的占位符,该循环对声明中定义的变量进行操作; 当然,这是一个循环,因此语句将应用于数组或集合中的每个项目。 并且,使用方括号( {
和}
),可以使用多个语句。
它的长短是创建一个变量,指示要迭代的数组或集合,然后对定义的变量进行操作。 列表中没有分配每个项目,因为for/in
为您处理。 当然,如果仍然不清楚,请继续阅读-有很多例子可以使这一点变得很清楚。
不过,在继续之前,我想提供一些有关for/in
工作原理的规范样式说明。 清单4显示了for/in
通用类型时的for/in
循环。 一旦编译器将其内部转换为普通的for
循环,该语句实际上就是它的样子。
你明白了吗? 实际上,编译器会将这段更短,更方便的for/in
语句转换为对编译器友好的for
循环-并且您不必承担首当其冲的工作。 这就是为什么我称此为方便,而不是简单。
清单4.处于转换状态且具有Iterable的for / in循环
for (Iterator<E> #i = (expression).iterator(); #i.hasNext(); ) {
declaration = #i.next();
statement
}
清单5是编译器转换之后的另一个for/in
,这次没有通用类型。 尽管有点简单,但正在发生同样的事情。 但是,无论哪种情况,您都可以轻松地从思想上(和程序上)将for/in
语句转换for/in
普通的for
语句。 一旦您可以在头脑中进行这种转换,事情就会变得非常容易。
清单5. for / in循环,经过转换,其中没有未参数化的类型
for (Iterator #i = (expression).iterator(); #i.hasNext(); ) {
declaration = #i.next();
statement
}
处理数组
现在,您已经掌握了基本的语义,现在您可以转向一些更具体的示例。 您已经了解了for/in
如何使用列表; 使用数组同样容易。 像集合一样,为数组分配值(如清单6所示),然后按常规将这些值一个接一个地剥离并对其进行操作。
清单6.简单的数组初始化
int[] int_array = new int[4];
String[] args = new String[10];
float[] float_array = new float[20];
在可能使用for
和一个计数器或索引变量的情况下,现在可以使用for/in
(当然,假设您正在使用Tiger。) 清单7显示了另一个简单示例:
清单7.用for / in遍历数组很容易
public void testArrayLooping(PrintStream out) throws IOException {
int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 };
// Print the primes out using a for/in loop
for (int n : primes) {
out.println(n);
}
}
这里不应该有任何特别的启示-这几乎是最基本的。 数组是类型化的,因此您确切知道数组中每个项目将需要哪种变量类型。 本示例创建变量(在本例中为n
),然后对该变量进行操作。 很简单,不是吗? 我告诉过你这里没有什么复杂的。
数组中的类型无关紧要-您只需为声明选择正确的类型。 在清单8中,一个数组包含List
。 因此,您实际上在这里有一系列的收藏夹。 尽管如此,使用for/in
还是很简单的。
清单8.还可以使用for / in遍历对象数组
public void testObjectArrayLooping(PrintStream out) throws IOException {
List[] list_array = new List[3];
list_array[0] = getList();
list_array[1] = getList();
list_array[2] = getList();
for (List l : list_array) {
out.println(l.getClass().getName());
}
}
您甚至可以在for/in
循环中分层,如清单9所示:
清单9.在for / in内部使用for / in并没有错!
public void testObjectArrayLooping(PrintStream out) throws IOException {
List[] list_array = new List[3];
list_array[0] = getList();
list_array[1] = getList();
list_array[2] = getList();
for (List l : list_array) {
for (Object o : l) {
out.println(o);
}
}
}
使用集合
再一次,简单是口号。 使用for/in
遍历集合几乎没有任何棘手或复杂的事情-它的工作原理就像您已经看到的一样,同时包含列表和数组。 清单10显示了一个对List
和Set
进行迭代的示例,并且几乎没有令人惊讶的方式。 不过,请遍历代码,并确保您清楚所发生的事情。
清单10.该程序中的许多简单循环演示了如何在for / in中使用
package com.oreilly.tiger.ch07;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ForInDemo {
public static void main(String[] args) {
// These are collections to iterate over below
List wordlist = new ArrayList();
Set wordset = new HashSet();
// Basic loop, iterating over the elements of an array
// The body of the loop is executed once for each element of args[].
// Each time through, one element is assigned to the variable word.
System.out.println("Assigning arguments to lists...");
for (String word : args) {
System.out.print(word + " ");
wordlist.add(word);
wordset.add(word);
}
System.out.println();
// Iterate through the elements of the List now
// Since lists have an order, these words should appear as above
System.out.println("Printing words from wordlist " +
"(ordered, with duplicates)...");
for (Object word : wordlist) {
System.out.print((String)word + " ");
}
System.out.println();
// Do the same for the Set. The loop looks the same but by virtue
// of using a Set, word order is lost, and duplicates are discarded.
System.out.println("Printing words from wordset " +
"(unordered, no duplicates)...");
for (Object word : wordset) {
System.out.print((String)word + " ");
}
}
}
清单11显示了该程序的输出(出于演示目的,在命令行中添加了一些数据):
清单11.输出的结果与您期望的一样,需要大量打印!
run-ch07:
[echo] Running Chapter 7 examples from Java 5.0 Tiger: A Developer's Notebook
[echo] Running ForInDemo...
[java] Assigning arguments to lists...
[java] word1 word2 word3 word4 word1
[java] Printing words from wordList (ordered, with duplicates)...
[java] word1 word2 word3 word4 word1
[java] Printing words from wordset (unordered, no duplicates)...
[java] word4 word1 word3 word2
打字的痛苦
到目前为止,在处理集合时,您已经看到for/in
使用了通用变量类型,例如Object
。 这很好,但是并没有真正利用Tiger的另一个重要功能:泛型(有时称为参数化类型 )。 我将离开仿制药developerWorks的关于这个问题即将教程的细节,但仿制药使for/in
如虎添翼。
请记住, for/in
语句的声明部分会创建一个变量,该变量具有要迭代的集合中每个项目的类型。 在数组中,这是非常特定的,因为数组是强类型的int[]
只能包含int-因此循环中没有类型转换。 当您通过泛型使用类型列表时,也有可能这样做。 清单12显示了一些简单的参数化集合:
清单12.在集合类型中添加参数意味着以后可以避免类型转换
List<String> wordlist = new ArrayList<String>();
Set<String> wordset = new HashSet<String>();
现在,您的for/in
循环可以抛弃普通的旧Object
并且更加具体。 清单13演示了这一点:
清单13.当您知道集合中的类型时,您的循环体可以更加特定于类型
for (String word : wordlist) {
System.out.print(word + " ");
}
作为一个更完整的示例,清单14采用了清单10所示的程序,并使用泛型列表和更特定的for/in
循环对其进行了扩展:
清单14.清单10可以重写以利用泛型
package com.oreilly.tiger.ch07;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ForInDemo {
public static void main(String[] args) {
// These are collections to iterate over below
List<String> wordlist = new ArrayList<String>();
Set<String> wordset = new HashSet<String>();
// Basic loop, iterating over the elements of an array
// The body of the loop is executed once for each element of args[].
// Each time through, one element is assigned to the variable word.
System.out.println("Assigning arguments to lists...");
for (String word : args) {
System.out.print(word + " ");
wordlist.add(word);
wordset.add(word);
}
System.out.println();
// Iterate through the elements of the List now
// Since lists have an order, these words should appear as above
System.out.println("Printing words from wordlist " +
"(ordered, with duplicates)...");
for (String word : wordlist) {
System.out.print((String)word + " ");
}
System.out.println();
// Do the same for the Set. The loop looks the same but by virtue
// of using a Set, word order is lost, and duplicates are discarded.
System.out.println("Printing words from wordset " +
"(unordered, no duplicates)...");
for (String word : wordset) {
System.out.print((String)word + " ");
}
}
}
当然,在这些情况下,类型转换不会完全消失。 但是,您将工作转移到了编译器上(对于那些对这种事情感兴趣的人,泛型或多或少是泛型的)。 在编译时,将检查所有这些类型,并且您将相应地得到任何错误。 如果其他人可以完成这项工作-那么,这对所有人都有好处,对吧?
将类与for / in集成
到目前为止,我只处理了Java预包装的类和类型的迭代-数组,列表,映射,集合和其他集合。 尽管这笔交易相当不错,但是任何编程语言的优点都是能够定义自己的类。 定制对象是任何大型应用程序的骨干。 本节仅涉及-for for/in
构造使用您自己的对象所涉及的概念和步骤。
新介面
到目前为止,您应该已经熟悉java.util.Iterator
接口了,以防万一,清单15显示了该接口,如Tiger中所示:
清单15.迭代器长期以来一直是Java语言的主流
package java.util;
public interface Iterator<E> {
public boolean hasNext();
public E next();
public void remove();
}
但是,要利用for/in
优势,您需要为域知识添加另一个接口java.lang.Iterable
。 清单16显示了此接口:
清单16. Iterable接口是for / in构造的基石
package java.lang;
public interface Iterable<E> {
public java.util.Iterator<E> iterator();
}
为了使您的对象或类可以使用for/in
,它需要实现Iterable
接口。 这为您提供了两种基本方案:
- 扩展已经实现
Iterable
的现有集合类(因此已经支持for/in
)。 - 通过定义自己的
Iterable
实现,手动处理迭代。
手动处理迭代
如果有可能,我强烈建议您让自定义对象扩展现有集合。 事情大大简化了,您可以避免各种杂乱的细节。 清单17显示了执行此操作的类:
清单17.扩展现有集合是利用for / in的简便方法
package com.oreilly.tiger.ch07;
import java.util.LinkedList;
import java.util.List;
public class GuitarManufacturerList extends LinkedList<String> {
public GuitarManufacturerList() {
super();
}
public boolean add(String manufacturer) {
if (manufacturer.indexOf("Guitars") == -1) {
return false;
} else {
super.add(manufacturer);
return true;
}
}
}
因为LinkedList
已经可以使用for/in
,所以您可以将此新类与for/in
一起使用for/in
而无需特殊代码。 清单18演示了这一点,以及使之成为可能的工作量:
清单18. Iterable接口是for / in构造的基石
package com.oreilly.tiger.ch07;
import java.io.IOException;
import java.io.PrintStream;
public class CustomObjectTester {
/** A custom object that extends List */
private GuitarManufacturerList manufacturers;
public CustomObjectTester() {
this.manufacturers = new GuitarManufacturerList<String>();
}
public void testListExtension(PrintStream out) throws IOException {
// Add some items for good measure
manufacturers.add("Epiphone Guitars");
manufacturers.add("Gibson Guitars");
// Iterate with for/in
for (String manufacturer : manufacturers) {
out.println(manufacturer);
}
}
public static void main(String[] args) {
try {
CustomObjectTester tester = new CustomObjectTester();
tester.testListExtension(System.out);
} catch (Exception e) {
e.printStackTrace();
}
}
}
手动处理迭代
在某些不常见的情况下-老实说,我很难考虑很多情况-当迭代自定义对象时,您可能需要执行特定的行为。 在这种情况下(不幸的是),您将不得不自己掌握一切。 清单19向您展示了它是如何工作的-它并不复杂,而且工作量很大,因此,我将让您自己遍历这段代码。 此类为文本文件提供了包装器,该文件列出了对其进行迭代的每一行。
清单19.耐心等待,您可以自己实现Iterable,在循环中提供自定义行为
package com.oreilly.tiger.ch07;
import java.util.Iterator;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/**
* This class allows line-by-line iteration through a text file.
* The iterator's remove() method throws UnsupportedOperatorException.
* The iterator wraps and rethrows IOExceptions as IllegalArgumentExceptions.
*/
public class TextFile implements Iterable<String> {
// Used by the TextFileIterator below
final String filename;
public TextFile(String filename) {
this.filename = filename;
}
// This is the one method of the Iterable interface
public Iterator<String> iterator() {
return new TextFileIterator();
}
// This non-static member class is the iterator implementation
class TextFileIterator implements Iterator<String> {
// The stream being read from
BufferedReader in;
// Return value of next call to next()
String nextline;
public TextFileIterator() {
// Open the file and read and remember the first line
// Peek ahead like this for the benefit of hasNext()
try {
in = new BufferedReader(new FileReader(filename));
nextline = in.readLine();
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
// If the next line is non-null, then we have a next line
public boolean hasNext() {
return nextline != null;
}
// Return the next line, but first read the line that follows it
public String next() {
try {
String result = nextline;
// If we haven't reached EOF yet...
if (nextline != null) {
nextline = in.readLine(); // Read another line
if (nextline == null)
in.close(); // And close on EOF
}
// Return the line we read last time through
return result;
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
// The file is read-only; we don't allow lines to be removed
public void remove() {
throw new UnsupportedOperationException();
}
}
public static void main(String[] args) {
String filename = "TextFile.java";
if (args.length > 0)
filename = args[0];
for (String line : new TextFile(filename))
System.out.println(line);
}
}
这里的大部分工作是实现Iterator
,然后由iterator()
方法返回它。 其他一切都非常简单。 正如您所看到的那样,手动实现Iterable
接口需要花费更多的时间,而不仅仅是扩展为您完成此工作的类。
你不能做什么
与所有美好事物一样-我确实认为for/in
是其中之一-存在局限性。 由于for/in
的设置方式,特别是因为它省略了Iterator
显式用法,因此使用此新构造有些事情是做不到的。
定位
最明显的限制是无法确定您在列表或数组(或自定义对象)中的位置。 为了刷新您的内存,清单20显示了如何使用典型的for
循环-请注意使用index变量不仅在列表中移动,而且还指示位置:
清单20.在“普通” for循环中使用迭代变量
List<String> wordList = new LinkedList<String>();
for (int i=0; i<args.length; i++) {
wordList.add("word " + (i+1) + ": '" + args[i] + "'");
}
这也不是附带的用法-这是常见的编程。 但是,您不能使用for/in
来完成这个非常简单的任务,如清单21所示:
清单21.在for / in循环中访问位置是不可能的
public void determineListPosition(PrintStream out, String[] args)
throws IOException {
List<String> wordList = new LinkedList<String>();
// Here, it's easy to find position
for (int i=0; i<args.length; i++) {
wordList.add("word " + (i+1) + ": '" + args[i] + "'");
}
// Here, it's possible to locate position
for (String word : wordList) {
out.println(word);
}
}
没有任何类型的计数器变量(或Iterator
),您在这里简直不走运。 如果你需要的位置,坚持用“普通” for
。 清单22显示了position的另一种常见用法-使用字符串:
清单22.另一个问题-字符串连接
StringBuffer longList = new StringBuffer();
for (int i=0, len=wordList.size(); i < len; i++) {
if (i < (len-1)) {
longList.append(wordList.get(i))
.append(", ");
} else {
longList.append(wordList.get(i));
}
}
out.println(longList);
移除物品
另一个限制是项目删除。 如清单23所示,不可能在列表迭代期间删除项目:
清单23.在for / in循环中删除一个项目是不可能的
public void removeListItems(PrintStream out, String[] args)
throws IOException {
List<String> wordList = new LinkedList<String>();
// Assign some words
for (int i=0; i<args.length; i++) {
wordList.add("word " + (i+1) + ": " '" + args[i] + "'");
}
// Remove all words with "1" in them. Impossible with for/in!
for (Iterator i = wordList.iterator(); i.hasNext(); ) {
String word = (String)i.next();
if (word.indexOf("1") != -1) {
i.remove();
}
}
// You can print the words using for/in
for (String word : wordList) {
out.println(word);
}
}
在大画面,这些都不多,因为他们是在何时使用指导方针限制for
,当使用for/in
。 也许是一件微妙的事情,但值得注意。
最重要的是,您永远不会在您需要 for/in
地方找到案例-至少据我所知。 取而代之的是,将其视为一个很好的便捷函数,可以使代码更清晰,更简洁,而无需陷入使读者头痛的简洁代码中。
翻译自: https://www.ibm.com/developerworks/java/library/j-forin/index.html