使用Guava是否有一种优雅的方式在转换集合时去除null值?

59

我有一个关于使用Google Collections(更新: Guava)简化一些集合处理代码的问题。

我有一堆“Computer”对象,并且我想最终得到它们的“资源id”的集合。 处理方式如下:

Collection<Computer> matchingComputers = findComputers();
Collection<String> resourceIds = 
    Lists.newArrayList(Iterables.transform(matchingComputers, new Function<Computer, String>() {
    public String apply(Computer from) {
        return from.getResourceId();
    }
}));

现在,getResourceId() 可能会返回 null(而且目前无法更改),但在这种情况下,我希望从生成的字符串集合中省略 null。

以下是过滤 null 的一种方法:

Collections2.filter(resourceIds, new Predicate<String>() {
    @Override
    public boolean apply(String input) {
        return input != null;
    }
});

你可以像这样将所有内容放在一起:

Collection<String> resourceIds = Collections2.filter(
Lists.newArrayList(Iterables.transform(matchingComputers, new Function<Computer, String>() {
    public String apply(Computer from) {
        return from.getResourceId();
    }
})), new Predicate<String>() {
    @Override
    public boolean apply(String input) {
        return input != null;
    }
});

但是对于这样一个简单的任务来说,这种方法很不优雅,更不用说可读性了!实际上,纯粹的 Java 代码(没有任何花哨的 Predicate 或 Function)可能会更加简洁:

Collection<String> resourceIds = Lists.newArrayList();
for (Computer computer : matchingComputers) {
    String resourceId = computer.getResourceId();
    if (resourceId != null) {
        resourceIds.add(resourceId);
    }
}

使用上述方法肯定也是一种选择,但出于好奇心(和想学习更多谷歌集合的愿望),你能否使用谷歌集合以更短或更优雅的方式做完全相同的事情

5个回答

81

在这里已经有一个谓词可以帮助你 - Predicates.notNull() - 你可以使用Iterables.filter()Lists.newArrayList()可以采用一个Iterable来进一步简化。

Collection<String> resourceIds = Lists.newArrayList(
  Iterables.filter(
     Iterables.transform(matchingComputers, yourFunction),
     Predicates.notNull()
  )
);
如果你实际上不需要一个 Collection,只需要一个 Iterable,那么 Lists.newArrayList() 调用也可以消失,这样你就又更加干净了一步!
我猜你可能会发现 Function 再次派上用场,并且最有用的声明方式是:
public class Computer {
    // ...
    public static Function<Computer, String> TO_ID = ...;
}

这将进一步清理(并促进重复使用)。


非常好的建议,谢谢!使用 Predicates.notNull() 并将函数放入常量中确实大大澄清了代码。 - Jonik
3
好的 :) 当我将一个函数用作转换器时,我喜欢将其隔离在一个静态方法中,并将其命名为intoXXX(),我觉得这样更易于阅读。在这种情况下,它可以是:transform(matchingCompters, intoResourceId())。 - sly7_7

35

使用FluentIterable(自Guava 12版本以来)可以获得更加“优美”的语法:

ImmutableList<String> resourceIds = FluentIterable.from(matchingComputers)
    .transform(getResourceId)
    .filter(Predicates.notNull())
    .toList();

static final Function<Computer, String> getResourceId =
    new Function<Computer, String>() {
        @Override
        public String apply(Computer computer) {
            return computer.getResourceId();
        }
    };

请注意,返回的列表是一个ImmutableList。不过,你可以使用copyInto()方法将元素倒入任意集合中。


14

我在寻找Java8版本的guava的Predicates#notNull()的等价物。从未想过要查看Objects - Chthonic Project
这是API文档:https://docs.oracle.com/javase/8/docs/api/java/util/Objects.html#nonNull-java.lang.Object- - amoebe

5

首先,在某个地方创建一个常量过滤器:

public static final Predicate<Object> NULL_FILTER =  new Predicate<Object>() {
    @Override
    public boolean apply(Object input) {
            return input != null;
    }
}

然后您可以使用:

Iterable<String> ids = Iterables.transform(matchingComputers,
    new Function<Computer, String>() {
        public String apply(Computer from) {
             return from.getResourceId();
        }
    }));
Collection<String> resourceIds = Lists.newArrayList(
    Iterables.filter(ids, NULL_FILTER));

您可以在代码的任何地方使用相同的空值过滤器。

如果您在其他地方使用相同的计算函数,您也可以将其设置为常量,只留下:

Collection<String> resourceIds = Lists.newArrayList(
    Iterables.filter(
        Iterables.transform(matchingComputers, RESOURCE_ID_PROJECTION),
        NULL_FILTER));

这肯定不如C#的写法好,但在Java 7中采用闭包和扩展方法后,这些都将变得更加易读易懂 :)


5
就我个人而言,我会称之为 NOT_NULL_FILTER。:) 而且在 Predicates 类中已经有一个静态方法可以实现这一点(请参见我的回答)。 - Cowan
1
@Cowan:这取决于你如何处理“filter” - 你可以说它过滤掉了空值。命名方面总体而言很让人头疼。不过,我认为它将要做的事情非常明显,因为反向操作会很愚蠢:) 不过,Predicates方法确实是个好方法。 - Jon Skeet

1
你可以像这样编写自己的方法。这将过滤掉任何返回 null 的函数的 apply 方法中的 null 值。
   public static <F, T> Collection<T> transformAndFilterNulls(List<F> fromList, Function<? super F, ? extends T> function) {
        return Collections2.filter(Lists.transform(fromList, function), Predicates.<T>notNull());
    }

接下来可以使用以下代码调用该方法。

Collection c = transformAndFilterNulls(Lists.newArrayList("", "SD", "DDF"), new Function<String, Long>() {

    @Override
    public Long apply(String s) {
        return s.isEmpty() ? 20L : null;
    }
});
System.err.println(c);

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