使用Google Guava过滤JavaBeans列表

24
在Java程序中,我有一个Bean列表,我想根据特定属性进行过滤。
例如,假设我有一个Person的JavaBean列表,其中Person有许多属性,包括“name”。
我还有一个名称列表。
现在我想找出所有名字在名称列表中的人。
使用Google Guava,执行此过滤的最佳方法是什么?
到目前为止,我考虑结合Guava和Apache BeanUtils,但那似乎不太优雅。
我还在这里找到了一个反射扩展库:http://code.google.com/p/guava-reflection/,但我不知道如何使用它(文档很少)。
有什么想法吗?
附:你能否看出我很想念Python的列表推导式?
9个回答

43

不要使用Guava,老式的方式去做。(来自一位Guava开发者的说法。)

List<Person> filtered = Lists.newArrayList();
for(Person p : allPersons) {
   if(acceptedNames.contains(p.getName())) {
       filtered.add(p);
   }
}

你可以使用Guava来实现这个功能,但Java并不是Python,试图将其变成Python只会让代码变得笨拙和难以阅读。 Guava的函数式工具应该谨慎使用,仅在它们对代码行数或性能提供明确且可衡量的好处时才使用。


3
你似乎认为在可读性和性能这两个方面之间只有妥协的余地。但正确性比可读性和性能更重要。如果你这样做,不仅需要测试谓词逻辑,还需要测试过滤逻辑。由于引入了可变状态,你不必要地使可能出错的事情翻倍(至少是翻倍)。如果你在代码库中多次进行过滤操作,那么这种方式会让你陷入困境...你为什么想以这种方式工作呢? - Daniel Alexiuc
2
我同意你应该尽量减少可变性的暴露。返回一个ImmutableList副本而不是ArrayList本身。但作为代码审阅者,我更喜欢阅读这段代码而不是另一段代码。代码中重要的部分更容易被识别。如果这段代码返回一个ImmutableList,那么我只需要一个或两个测试以及谓词测试就可以满意了。 - Louis Wasserman
1
这是官方Guava的立场。我写的Guava用户指南在http://goo.gl/TCHHz上提供更多详细信息。我应该补充一点,随着Java 8和Project Lambda的出现,我们的立场将会发生180度的变化...尽管到那时,我们可能会重写库中所有函数部分。 ;) - Louis Wasserman
我很困惑这怎么可能能够工作(甚至编译)。filter的谓词参数必须接受Person(或Person的基类/接口)。acceptedNames大概是一组字符串的集合/列表。即使它以某种方式编译过了,它怎么知道它应该检查姓名属性是否在acceptedNames中而不是其他字符串(或其他属性)呢? - Vitali
你...完全正确。(虽然它可以编译,很奇怪。) - Louis Wasserman
显示剩余3条评论

23
Iterable<Person> filtered = Iterables.filter(allPersons, new Predicate<Person>() {
    @Override
    public boolean apply(Person p) {
        return acceptedNames.contains(p.getName());
    }
});

如果你的名字列表很大,最好将其转换为Set(最好是HashSet),并在该集合上调用contains方法,而不是在列表上调用,因为对于HashSet来说,contains方法的时间复杂度为O(1),而对于List来说,时间复杂度为O(n)。


1
应该将接受名称的列表转换为集合,而不是人员列表。这就是所调用的contains方法。将人员列表转换为集合没有附加值。 - JB Nizet
@Daniel在另一个答案中的评论提供了一些背景,解释了为什么在某些情况下这是更可取的。 - studgeek

5

解释句子中的疑惑:

到目前为止,我考虑将Guava与Apache beanutils结合使用,但这似乎不太优雅。

尽管Java非常流行,但缺乏一流函数支持*,这在Java 8中可能会发生改变,届时您将能够执行以下操作:

Iterable <Person> filtered = filter(allPersons, (Person p) -> acceptedNames.contains(p.getName()));

使用lambda表达式可以使代码更加优雅。

在那之前,你需要在以下几种方式中做出选择:

  • 老派的方式(如@Louis所写)
  • 冗长的Guava过滤器(@JB的回答)
  • 或者其他函数式Java库(@superfav的回答)

我还想补充@Lois的回答,Guava-way是创建不可变集合,因为它们比不可修改的集合更好,这也在Joshua Bloch的《Effective Java》第15项“最小化可变性”中有所描述**

ImmutableList.Builder<Person> builder = ImmutableList.builder();
for (final Person p : allPersons) {
    if (acceptedNames.contains(p.getName())) {
        builder.add(p);
    }
}
ImmutableList<Person> filtered = builder.build();

(ImmutableList.Builder 在内部创建临时的 ArrayList 是实现细节。)

*: 这让我很困扰,我来自 Python、JavaScript 和 Perl 的世界,在这些语言中函数被更好地处理

**: Guava 和 Bloch 在许多方面都是紧密耦合的 ;)


4

我完全赞同Louis和JB的答案。我不知道guava-reflection,也许LambdaJ是你正在寻找的东西:

// set up
Person me = new Person("Favio");
Person luca = new Person("Luca");
Person biagio = new Person("Biagio");
Person celestino = new Person("Celestino");
Collection<Person> meAndMyFriends = asList(me, luca, biagio, celestino);

// magic
Collection<Person> filtered = filter(having(on(Person.class).getName(),
                                            isOneOf("Favio", "Luca")),
                                     meAndMyFriends);

// test
assertThat(filtered, hasItems(me, luca));
assertEquals(2, filtered.size());

也许Scala、Clojure或Groovy是你正在寻找的技术...

2

身为 guava-reflection 的开发者,很抱歉我在这么早的阶段就放弃了这个项目(我有一份全职工作还有家庭:-))。我的愿景是:

Iterable<Object> thingsWithNames = 
    Iterables.filter(someData,
                     // this is a Predicate, obviously
                     BeanProperties.hasBeanProperty("name", String.class));

现有的代码完成了大约60%,如果您有兴趣,请联系我,也许我们可以一起完成这个项目。


0

使用Java8,您可以使用Collection.removeIf()

List<Person> theList = ...;
theList.removeIf(
    (Person p)->"paul".equals(p.getName())
);

这当然会修改当前列表。


0

这是一个使用泛型的例子,使用Guava和BeanUtils来过滤任何列表以匹配请求

/**
 * Filter List
 * 
 * @param inputList
 * @param requestMatch
 * @param invokeMethod
 * @return
 */
public static <T> Iterable<T> predicateFilterList(List<T> inputList, final String requestMatch,
        final String invokeMethod) {
    Predicate<T> filtered = new Predicate<T>() {
        @Override
        public boolean apply(T input) {
            boolean ok = false;
            try {
                ok = BeanUtils.getProperty(input, invokeMethod).equalsIgnoreCase(requestMatch);
            }
            catch (Exception e) {
                e.printStackTrace();
            }
            return ok;
        }
    };
    return Iterables.filter(inputList, filtered);
}

0
如果您在单线程应用程序中使用LinkedList(或任何其他集合,其删除操作不是非常费力),则最有效的解决方案是:
final Iterator<User> userIterator = users.iterator();
while (userIterator.hasNext()) {
    if (/* your condition for exclusion */) {
        userIterator.remove();
    }
}

哎呀!这样做不行,因为你会在并发访问列表时运行。 - Abderrazak BOUADMA

0

使用Java8的风格,您可以使用stream + filter来实现您的目标。

persons.stream()
            .filter(p -> names.contains(p.getName()))
            .collect(Collectors.toList());

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