使用for / in增强Java 5.0中的循环

越短越好

这是硬核计算机程序员的最基本公理之一:因为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的最根本区别是您不必使用计数器(通常称为icount )或Iterator 。 请查看清单1,该清单显示了使用Iteratorfor循环:

清单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显示了一个对ListSet进行迭代的示例,并且几乎没有令人惊讶的方式。 不过,请遍历代码,并确保您清楚所发生的事情。

清单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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值