如何从列表中筛选元素

时间:2012-10-29 14:19:34

标签: java guava

我正在尝试实现一个相当简单的方法,我想过滤一个列表。这是一个File对象列表,应该只有一个以.asp结尾的文件 - 我希望从列表中排除一个。请记住,我实际上并不想从列表中删除此文件,我只是希望能够忽略该列表的特定迭代。

我的原始(蛮力)实现看起来像这样:

public List<File> getSurveyFiles() throws Exception {
    List<File> surveyFiles = new ArrayList<File>(files.size() - 1);

    for ( File f : files ) {
        if ( !f.getName().endsWith(".asp") ) {
            surveyFiles.add(f);
        }
    }

    return surveyFiles;
}

它有效,但是我创建第二个列表并从一个列表复制到另一个列表时感觉非常浪费。

我玩弄的另一个选择是使用guava-libraries(http://code.google.com/p/guava-libraries/)并利用他们的过滤功能,如下所示:

public class SurveyFileControllerPredicate implements Predicate<File> {

    @Override
    public boolean apply(File file) {
        return file.getName().endsWith(".asp");
    }
}

...

public Iterable<File> getSurveyFiles() throws Exception {

    return Iterables.filter(
        files,
        Predicates.not(new SurveyFileControllerPredicate())    
    );

}

filter的实现会在迭代时删除.asp文件,而不是提前删除。所以这段代码的好处是不会创建第二个List,但我觉得这会让我的代码变得更复杂。

我还没有考虑其他更简单的实现吗?

在整个方案中,我选择的实现可能并不重要。我只是好奇其他开发人员如何解决这个问题以及他们会选择哪种选择。

感谢。

3 个答案:

答案 0 :(得分:5)

您可以使用toString()函数编写正则表达式匹配谓词:

public Iterable<File> getSurveyFiles() {
  return Iterables.filter(files, Predicates.compose(
      Predicates.not(Predicates.containsPattern("\\.asp$")),
      Functions.toStringFunction()));
}

答案 1 :(得分:2)

在某些时候,我写了这两个非常一般的助手类来处理这样的问题:

public abstract class IteratorFilter<E> implements Iterator<E> {
  private final Iterator<E> iterator;

  private E next = null;

  public IteratorFilter(Iterator<E> iterator) {
    this.iterator = iterator;
  }

  @Override
  public boolean hasNext() {
    if (next!=null) return true;
    while (iterator.hasNext()) {
      next = iterator.next();
      if (keep(next)) return true;
    }
    return false;
  }

  @Override
  public E next() {
    if (next==null)
      do next = iterator.next(); while (!keep(next));
    E result = next;
    next = null;
    return result;
  }

  @Override
  public void remove() {
    iterator.remove(); // Specs require: throw new UnsupportedOperationException();
  }

  protected abstract boolean keep(E item);
}

public abstract class IterableFilter<T> implements Iterable<T> {

  private final Iterable<T> iterable;

  public IterableFilter(Iterable<T> iterable) {
    this.iterable = iterable;
  }

  @Override
  public Iterator<T> iterator() {
    return new IteratorFilter<T>(iterable.iterator()) {
      @Override
      protected boolean keep(T item) {
        return IterableFilter.this.keep(item);
      }
    };
  }

  protected abstract boolean keep(T item);
}

有了这些,你可以简单地这样做:

public Iterable<File> getSurveyFiles() {
  return new IterableFilter<File>(files) {
    @Override
    protected boolean keep(File item) {
      return !item.getName().endsWith(".asp");
    }
  };
}

它与Guava Predicate方法基本相同,只是您不需要跟踪谓词对象,也不会引入新的库依赖项。

答案 2 :(得分:0)

如果您愿意在迭代站点编写过滤(而不是编写一个返回过滤后的副本或视图的函数),那么Java 8流就会变得非常简单:

files.stream().filter(f -> !f.getName().endsWith(".asp")).forEachOrdered(f -> {
    //process file f
});

如果您只在几个地方进行此过滤,则这比编写返回过滤后的副本或视图的方法更简洁,并使过滤操作保持接近使用过滤列表的位置。如果你在很多地方进行过滤并且可能希望稍后以不同的方式过滤列表,那么编写方法可能会更好 - 但它可以是一个返回Stream的方法:

public Stream<File> getSurveyFiles() {
    return files.stream().filter(f -> !f.getName().endsWith(".asp"));
}

然后,您可以在返回值上调用forEachOrdered。如果您需要非流操作,请调用iterator以获取迭代器或.collect(Collectors.toList())以获取列表的过滤副本。