【问题标题】:Is there an elegant way to remove nulls while transforming a Collection using Guava?有没有一种优雅的方法可以在使用 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 收藏集以更短或更优雅的方式做同样的事情?

【问题讨论】:

    标签: java collections refactoring guava


    【解决方案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.

      【讨论】:

      【解决方案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 方法的调用很好。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-26
        • 1970-01-01
        • 1970-01-01
        • 2019-07-11
        • 2010-12-10
        • 2021-08-14
        相关资源
        最近更新 更多