Guava 迭代器,以及在列表对象中迭代列表的方法

14

我有以下示例代码,包含3层嵌套的for循环。

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

是否有任何方法可以使用Guava和迭代器模仿这个嵌套的for循环?我一直在努力寻找一个合适的例子,但没有太多运气,我想知道是否有人能够帮助我?我的同事提到了使用过滤器。

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


3
你可以嵌套你的映射。依我之见,至少对于外部循环来说,嵌套循环可能更简单。 - Peter Lawrey
3
第三行应该是 "continent.getCountries()",对吗? - Chris
你可以使用Guava的“transform”和“concat”来创建一个<Continent,Country,City>三元组的单一列表,然后对其进行迭代,但至少在Java 7中,代码会相当丑陋。我建议仍然使用嵌套循环。 - Chris
5个回答

12

正如Peter Lawrey所评论的那样,这几乎肯定会更简单,因为可以使用嵌套循环。此外,Guava文档给出了以下警告:

在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循环,并不是其中之一。


4
通过使用FluentIterable.transformAndConcat(),你可以将转换链接起来,而不是嵌套它们。 - Frank Pavageau
@FrankPavageau:没错。但即使这样会更加简洁,它仍然比嵌套循环更难看、更难读。 - ig0774

8

您可以为以下内容定义静态函数:
• 在Continent、Continents或Functions中定义getCountries()函数
• 在Country、Countries或Functions中定义getCities()函数

现在您可以进行如下操作...

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

如果:
• 您经常使用Guava。
• 并且对于您定义的函数和谓词的位置有特定的规则/想法。
• 并且有各种需要过滤或搜索的(复杂)内容。
那么它可以是一个很好的福音,并且可以使许多情况变得更加容易。我知道我很高兴我这样做了。

如果您很少使用它,那么我必须同意@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的建议保留嵌套循环

P.S. 不需要过滤器。


2

1
我同意其他人的观点,嵌套循环是最有效的方法。但是:我会将每个循环层级提取到单独的方法中,以保持可读性并确保每个方法只做一件事情。
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函数进行单元测试。谢谢 :) - GobiasKoffi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接