有没有一种优雅的方法可以在使用 Guava 转换集合时删除空值?

Posted

技术标签:

【中文标题】有没有一种优雅的方法可以在使用 Guava 转换集合时删除空值?【英文标题】:Is there an elegant way to remove nulls while transforming a Collection using Guava? 【发布时间】:2010-12-20 15:10:16 【问题描述】:

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

我有一堆“计算机”对象,我想以它们的“资源 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(现在不能更改它),但在这种情况下,我想从生成的 String 集合中省略 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);
    

使用上述方法当然也是一种选择,但出于好奇(以及想了解更多 Google 收藏集),您能否使用 Google 收藏集以更短或更优雅的方式做同样的事情?

【问题讨论】:

【参考方案1】:

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() 方法将元素倒入任意集合中。

【讨论】:

【参考方案2】:

Predicates 中已经有一个谓词可以在这里为您提供帮助 -- 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() 并将函数放在一个常量中确实使代码更加清晰。 太棒了:)。当我使用函数作为转换时,我喜欢将它隔离在一个静态方法中并将其命名为 intoXXX(),我觉得它很容易阅读。在这种情况下,它可能是:transform(matchingCompters, intoResourceId()).【参考方案3】:

它比@Jon Skeet expected 花费了更长的时间,但 Java 8 流确实让这变得简单:

List<String> resourceIds = computers.stream()
    .map(Computer::getResourceId)
    .filter(Objects::nonNull)
    .collect(Collectors.toList());

如果你愿意,也可以使用.filter(x -&gt; x != null); the difference is very minor.

【讨论】:

我正在寻找与 guava 的 Predicates#notNull() 等效的 Java8。没想到看Objects 这里是 API 文档:docs.oracle.com/javase/8/docs/api/java/util/…【参考方案4】:

您可以像这样编写自己的方法。这将为从 apply 方法返回 null 的任何函数过滤掉 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);

【讨论】:

【参考方案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 中通过闭包和扩展方法变得更好 很多 :)

【讨论】:

我个人称它为 NOT_NULL_FILTER。 :) Predicates 类中已经有一个静态方法(见我的回答)。 @Cowan:这取决于您如何对待“过滤器”——您可能会争辩说它会过滤 空值。这是命名方面的普遍痛苦。但是,我认为它会做什么很明显,因为反过来会很愚蠢:) 不过对 Predicates 方法的调用很好。

以上是关于有没有一种优雅的方法可以在使用 Guava 转换集合时删除空值?的主要内容,如果未能解决你的问题,请参考以下文章

有没有一种优雅的方法可以将 BQ 嵌套字段转换为 key:value JSON?

Guava学习笔记:Optional优雅的使用null

优雅代码14-guava精选方法及eventBus观察者模式源码解析

有没有一种优雅的方法可以用 XSLT 添加多个 HTML 类?

没用 Java 8,怎么优雅地避免空指针?

没用 Java 8,怎么优雅地避免空指针?