Reputation: 3444
I'm trying to implement a fairly simple method in which I want to filter a list. This is a list of File objects and there should be exactly one file that ends with .asp - I want that one excluded from the list. Keep in mind that I don't actually want to remove this file from the list, I just want to be able to ignore it for a specific iteration of that list.
My original (brute force) implementation looked like this:
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;
}
It works, but it feels very wasteful in the fact that I am creating a second list and doing a lot of copying from one list to another.
Another option I've toyed with is to use guava-libraries (http://code.google.com/p/guava-libraries/) and utilizing their filter function, like this:
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())
);
}
The implementation of filter removes the .asp file at iteration time, rather than ahead of time, so this code has the benefit of not making a second List, but I feel that it makes my code more complex.
Are there other, simpler, implementations that I'm not considering?
In the whole scheme of things, which implementation I choose probably doesn't matter. I'm just curious how other developers would tackle this and what option they would choose.
Thanks.
Upvotes: 3
Views: 277
Reputation: 13653
If you're willing to write the filtering at the iteration site (rather than write a function that returns a filtered copy or view), Java 8 streams make this very simple:
files.stream().filter(f -> !f.getName().endsWith(".asp")).forEachOrdered(f -> {
//process file f
});
If you only do this filtering in a few places, this is more concise than writing a method that returns a filtered copy or view, and keeps the filtering operation close to where the filtered list is used. If you do this filtering in many places and may want to filter the list differently later, writing a method might be better -- but it can be a method that returns a Stream:
public Stream<File> getSurveyFiles() {
return files.stream().filter(f -> !f.getName().endsWith(".asp"));
}
You can then call forEachOrdered
on the return value. If you need a non-stream operation, call iterator
to get an iterator or .collect(Collectors.toList())
to get a filtered copy of the list.
Upvotes: 0
Reputation: 12742
At some point, I wrote myself these two very general helper classes that handle problems like this:
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);
}
and:
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);
}
With these, you can simply do this:
public Iterable<File> getSurveyFiles() {
return new IterableFilter<File>(files) {
@Override
protected boolean keep(File item) {
return !item.getName().endsWith(".asp");
}
};
}
It's essentially the same approach as the Guava Predicate method, except that you don't need to keep track of the predicate object and you don't introduce a new library dependency.
Upvotes: 2
Reputation: 110046
You could compose a regex matching predicate with the toString()
function:
public Iterable<File> getSurveyFiles() {
return Iterables.filter(files, Predicates.compose(
Predicates.not(Predicates.containsPattern("\\.asp$")),
Functions.toStringFunction()));
}
Upvotes: 5