【问题标题】:Guava iterators, and iterating over a list within a list object番石榴迭代器,并迭代列表对象中的列表
【发布时间】:2013-01-15 15:00:49
【问题描述】:

我有以下示例代码,它由 3 个嵌套的 for 循环组成。

for(Continent continent : continentList) 
{
    for(Country country : continent.getCountries())
    {
        for(City city : country.getCities())
        {
            //Do stuff with city objects
        }
    }
}

有没有办法使用 Guava 和迭代器来模拟这种嵌套的 for 循环?我一直在尝试找到一个合适的例子,但运气不佳,我想知道是否有人可以帮助我?我的一位同事提到使用过滤器。

编辑:修复示例代码中的小错误

【问题讨论】:

  • 你可以嵌套你的映射。恕我直言,嵌套循环可能更简单,至少对于外部循环。
  • 在第 3 行,不应该是“continent.getCountries()”吗?
  • 您可以使用 guava "transform" 和 "concat" 创建一个 三元组列表,然后对其进行迭代,但至少使用 Java 7,代码将是相当丑陋。我会留在嵌套循环中。

标签: java iterator guava


【解决方案1】:

正如 Peter Lawrey 评论的那样,这几乎肯定会像嵌套循环一样简单。此外,Guava documentation 给出了这个警告:

命令式代码应该是您的默认设置,也是您在 Java 中的首选 7. 除非您绝对确定以下其中一项,否则不应使用功能性成语:

  • 使用功能性惯用语将净节省代码行数 为您的整个项目。移动定义 一个函数到另一个文件或一个常量,没有帮助。
  • 为了提高效率,您需要一个延迟计算的转换视图 集合,不能满足于显式计算的集合。 此外,您已经阅读并重读了 Effective Java,第 55 条,以及 除了遵循这些说明之外,您实际上已经完成了 基准测试证明这个版本速度更快,并且可以引用 数字来证明。

请确定,在使用 Guava 的函数时 实用程序,传统的命令式做事方式不是 更具可读性。试着写出来。有那么糟糕吗?是不是更多 比您使用的荒谬笨拙的功能方法更易读 要试试吗?

但是,如果您坚持忽略建议,则可以使用类似这种怪物的东西(注意我实际上并没有尝试编译或运行它):

FluentIterable.from(continentList)
    .transform(new Function<Continent, Void>() {
        public Void apply(Continent continent) {
            return FluentIterable.from(continent.getCountries())
                .transform(new Function<Country, Void>() {
                    public Void apply(Country country) {
                        return FluentIterable.from(country.getCities())
                            .transform(new Function<City, Void>() {
                                public Void apply(City city) {
                                    // do stuff with city object
                                    return null;
                                }
                            });
                    }
                });
        }
    });

现在问问自己:您想维护哪个?哪个效率最高?

Guava 的函数式习语有一些有效的用例。替换 Java for 循环,甚至是嵌套的 for 循环,都不是其中之一。

【讨论】:

  • 使用FluentIterable.transformAndConcat(),您可以链接转换而不是嵌套它们。
  • @FrankPavageau:是的。但即使这样会更简洁一些,它仍然会比嵌套循环更丑陋且可读性更低。
  • @ig0774 感谢您的专业提示 :)
【解决方案2】:

您可以为以下对象定义静态函数:
• 大陆、大陆或函数中的 getCountries()
• 国家、国家或函数中的 getCities()

现在你可以做类似...

FluentIterable.from(continentList)
    .transformAndConcat(Continent.getCountriesFunction())
    .transformAndConcat(Country.getCitiesFunction())
    . //filter //tranform //find //toList() //etc.

如果:
• 您经常(更多)这样使用番石榴。
• 对定义函数和谓词的位置有一定的规则/想法。
• 并且有不同(复杂)的东西要过滤或搜索。
然后它可以是一个很大的好处,可以使许多情况变得容易一些。我知道我很高兴我做到了。

如果您很少使用它,那么我将不得不同意@Louis Wasserman。那么麻烦就不值得了。此外,像其他示例一样将函数和谓词定义为匿名内部类......真的很难看。

【讨论】:

    【解决方案3】:

    另一个怪物,使用 AbstractIterator:

        class CityIterable implements Iterable<City> {
            List<Continent> continents;
    
            CityIterable(List<Continent> continents) {
                this.continents = continents;
            }
    
            @Override
            public Iterator<City> iterator() {
                return new AbstractIterator<City>() {
                    Iterator<Continent> continentIterator = continents.iterator();
                    Iterator<Country> countryIterator;
                    Iterator<City> cityIterator;
    
                    @Override
                    protected City computeNext() {
                        if (cityIterator != null && cityIterator.hasNext()) {
                            return cityIterator.next();
                        }
                        if (countryIterator != null && countryIterator.hasNext()) {
                            cityIterator = countryIterator.next().getCities().iterator();
                            return computeNext();
                        }
                        if (continentIterator.hasNext()) {
                            countryIterator = continentIterator.next().getCountries().iterator();
                            return computeNext();
                        }
                        return endOfData();
                    }
                };
            }
        }
    

    然后调用它:

        for (City city: new CityIterable(continentList)) {
            System.out.println(city.name);
        }
    

    鉴于这种怪异现象,请遵循 ig0774 的建议并保留嵌套循环

    附:无需过滤器。

    【讨论】:

      【解决方案4】:

      不,没有简单的方法。此外,它会比您问题中的 for-each 循环更冗长。

      参见http://code.google.com/p/guava-libraries/issues/detail?id=218#c5http://code.google.com/p/guava-libraries/wiki/FunctionalExplained 中的注意事项

      【讨论】:

        【解决方案5】:

        我同意其他人的观点,即嵌套循环是最有效的方法。但是:我会将每个循环级别提取到一个单独的方法中,以保持可读性并确保每个方法都只做一件事:

        public void doStuffWithWorld(World world){
            for (Continent continent : world.getContinents()) {
                doStuffWithContinent(continent);
            }
        }
        
        private void doStuffWithContinent(Continent continent) {
            for (Country country : continent.getCountries()) {
                doStuffWithCountry(country);
            }
        }
        
        private void doStuffWithCountry(Country country) {
            for(City city : country.getCities()){
                doStuffWithCity(city);
            }
        }
        
        private void doStuffWithCity(City city) {
            // do stuff here
        }
        

        如果您需要通过不同的级别携带一些状态,您有几个选择:将它们放在包含类的成员字段中,将第二个参数传递给可以是地图或自定义对象的所有方法。

        【讨论】:

        • 我真的很喜欢你的建议;如果需要,我还将对每个单独的 Looper 功能进行单元测试。谢谢:)
        猜你喜欢
        • 2012-10-11
        • 1970-01-01
        • 2014-04-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-02
        • 2015-12-12
        • 2019-07-05
        相关资源
        最近更新 更多