2012-10-29 66 views
3

我试图实现一个相当简单的方法,在该方法中我想筛选一个列表。这是一个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; 
} 

它的工作原理,但它在我创建第二个列表,并做了很多复制从一个列表这样的事实感到非常浪费另一个。

我和玩弄另一种选择是使用番石榴库(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())  
    ); 

} 

过滤器的实现会在迭代时删除.asp文件,而不是提前,因此此代码具有不创建第二个List的好处,但我觉得它使我的代码更加复杂。

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

在事物的整个方案中,我选择的实现可能并不重要。我只是好奇其他开发者如何解决这个问题,他们会选择什么样的选择。

谢谢。

+2

你迭代的情况下,其中[的FilenameFilter(http://docs.oracle.com/javase/6/docs/api/java/io/FilenameFilter.html)是合适的,虽然这些文件? –

+0

当您填充原始列表本身时,是否可以添加过滤器?我的意思是,原始列表人口是通过您的代码完成的,还是您收到预先填充的代码? –

回答

5

你可以编写一个正则表达式匹配谓语与toString()功能:

public Iterable<File> getSurveyFiles() { 
    return Iterables.filter(files, Predicates.compose(
     Predicates.not(Predicates.containsPattern("\\.asp$")), 
     Functions.toStringFunction())); 
} 
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 Iterable<File> getSurveyFiles() { 
    return new IterableFilter<File>(files) { 
    @Override 
    protected boolean keep(File item) { 
     return !item.getName().endsWith(".asp"); 
    } 
    }; 
} 

它基本上与Guava Predicate方法一样,只是你不需要需要跟踪谓词对象,并且不引入新的库依赖关系。

+1

你不需要用Guava跟踪谓词对象。顺便说一句,'删除()'不应该在一个'Iterator'能过滤支持:调用'hasNext()之后','删除()'将不再移除最后调用返回'下一个对象() '因为它被指定做。它甚至可以删除迭代器不包含的元素(迭代器跳过)。 – ColinD

+0

@ColinD完全同意。其实我在想的hasNext() - >删除()的场景,但决定离开我的代码,我用它的方式,因为(至少在我的情况)我从来没有遇到过的情况下,我删除的对象,我没有检查next()第一次。但是我在代码中添加了一条评论,因为您绝对正确。 –

+0

@ColinD:只是为了完整性,你觉得有可能正确地做出remove()方法的工作,通过使用并行运行的两个迭代器?或者在其中一个调用remove()会使另一个的状态失效? –

0

如果你愿意写在迭代网站过滤(而不是写一个返回筛选副本或视图的功能) ,Java的8分汊,使这个非常简单:

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

如果你只是做了几个地方这种过滤,这比写一个返回筛选副本或视图的方法更简洁,并保持过滤操作接近过滤的列表在哪里 用过的。如果你在很多地方这种过滤和可能需要不同的后过滤列表,写一个方法可能会更好 - 但它可以是一个返回流的方法:

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

你可以调用forEachOrdered上返回值。如果你需要一个非流操作,调用iterator得到一个迭代器或.collect(Collectors.toList())以获取列表的筛选副本。