2012-10-29 63 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())以獲取列表的篩選副本。