美文网首页
Effective Java(3rd)-Item58 与传统的f

Effective Java(3rd)-Item58 与传统的f

作者: 难以置信的优雅 | 来源:发表于2018-09-18 15:02 被阅读0次

      正如在第45项中所讨论的,一些任务最好使用流来完成,其他任务最好使用迭代。下面是一个传统的for循环来遍历一个集合:


    image.png

      这些习惯用法比while循环更好(item57 ),但是它们并不完美。迭代器和索引变量都很混乱—您只需要元素。此外,它们代表了出错的机会。迭代器在每个循环中出现三次,索引变量出现四次,这使您有很多机会使用错误的变量。如果这样做,就不能保证编译器会捕捉到问题。最后,这两个循环非常不同,引起了对容器类型的不必要注意,并且增加了更改该类型的(小的)麻烦。

      for-each循环(官方称为“enhanced for语句”)解决了所有这些问题。它通过隐藏迭代器或索引变量来消除混乱和出错的机会。由此产生的习惯用法同样适用于集合和数组,从而简化了将容器的实现类型从一种转换为另一种的过程:


    image.png

      当您看到冒号(:)时,请将其读作“in”。因此,上面的循环读作“for each element e in elements”。使用for-each循环不会降低性能,即使对于数组也是如此:它们生成的代码本质上与手工编写的代码相同。
      当涉及到嵌套迭代时,for-each循环相对于传统for循环的优势甚至更大。下面是人们在进行嵌套迭代时经常犯的一个错误:

    image.png

      如果你没有发现这个bug,不要感到难过。许多专业程序员都曾犯过这样或那样的错误。问题是,对于外部集合(suit),下一个方法在迭代器上调用了太多次。它应该从外部循环调用,因此每花色调用一次,但它是从内部循环调用的,因此每一张牌调用一次。在suit用完之后,循环抛出NoSuchElementException。
      如果您真的很不幸,并且外部集合的大小是内部集合大小的倍数(可能因为它们是相同的集合),循环将正常终止,但是它不会执行您想要的操作。例如,考虑一下打印一对骰子的所有可能卷的错误尝试:


    image.png

      程序不会抛出异常,但它只打印6个“double”(从“ONE ONE”到“six six”),而不是预期的36个组合。
      要修复这些例子中的错误,您必须在外部循环的范围内添加一个变量来保存外部元素:


    image.png

      相反,如果使用嵌套for-each循环,问题就会消失。生成的代码尽可能简洁:


    image.png

      不幸的是,有三种常见的情况是你不能分别使用的:

    • 有损的过滤——如果需要遍历删除选定元素的集合,则需要使用显式迭代器,以便调用其删除方法。通过使用Collection在Java 8中添加的removeIf方法,您通常可以避免显式遍历。
    • 转换——如果需要遍历一个列表或数组并替换其元素的部分或全部值,则需要列表迭代器或数组索引来替换元素的值。
    • 并行迭代——如果您需要并行地遍历多个集合,那么您需要显式地控制迭代器或索引变量,以便所有迭代器或索引变量都可以同步进行(如上面错误的card和dice示例中无意中演示的那样)。
        如果您发现自己处于这些情况中的任何一种,请使用普通的for循环,并警惕本项目中提到的陷阱。
        for-each循环不仅允许您遍历集合和数组,还允许您遍历实现Iterable接口的任何对象,该接口由一个方法组成。接口如下:
      image.png
        它有点难以实现Iterable如果你不得不从头开始编写自己的迭代器实现,但是如果你正在写一个类型代表一组元素,你绝对应该考虑把它实现Iterable,即使你选择不实现集合。这将允许用户使用for-each循环遍历类型,他们将永远感激不尽。
        总之,for-each循环在清晰度、灵活性和bug预防方面比传统的for循环提供了引人注目的优势,并且没有性能损失。尽可能使用for-each循环而不是for循环。
      本文写于2019.7.18,历时1天

    相关文章

      网友评论

          本文标题:Effective Java(3rd)-Item58 与传统的f

          本文链接:https://www.haomeiwen.com/subject/nmtpnftx.html