开发者

Easy way to filter elements from a collection in Java?

I want to write a method that removes all elements from a collection that follow a certain pattern. In functional languages, I would use filter() with a lambda expression.开发者_JS百科 However, in Java, it seems I'm stuck with this:

public void removeAllBlueCars() {
    LinkedList<Car> carsToRemove = new LinkedList<Car>();
    for (Car c : cars) {
        if (c.getCarColor() == Color.BLUE) {
            carsToRemove.add(c);
        }
    }
    cars.removeAll(carsToRemove );
}

Removing elements directly causes a ConcurrentModificationException. Is there a better way to do this without resorting to Google Collections?


With Java 8, you can filter with a lambda expression using Collection.removeIf.

cars.removeIf(c -> c.getCarColor() == Color.BLUE);


Maybe you could use iterators, which are a little more efficient:

public void removeAllBlueCars() {
    Iterator<Car> carsIterator = cars.iterator();
    while (carsIterator.hasNext()) {
        Car c = carsIterator.next();
        if (c.getCarColor() == Color.BLUE) {
            carsIterator.remove();
        }
    }
}

Also, if you want to make this solution more generic, I'd suggest you something like:

public interface Filter<T> {

    public boolean shouldRemove(T t);

}

And you could use it like this:

public void removeCars(Filter<Car> filter) {
    Iterator<Car> carsIterator = cars.iterator();
    while (carsIterator.hasNext()) {
        Car c = carsIterator.next();
        if (filter.shouldRemove(c)) {
            carsIterator.remove();
        }
    }
}

Your method gets called like this:

removeCars(new Filter<Car>() {

    public boolean shouldRemove(Car car) {
        return car.getCarColor() == Color.BLUE;
    }

});


You could iterate through the list using a ListIterator, which has a remove method.

Btw you should declare your list as List<Car> - program for interfaces, not implementation.


You can use CollectionUtils.filter(). It works with an Iterator, so it should have no problems removing items directly from the Collection. It is another dependency though. If you want the code standalone it would be:

public interface Predicate {
    boolean evaluate(Object o);
}

public static void filter(Collection collection, Predicate predicate) {
if ((collection != null) && (predicate != null))
    for (Iterator it = collection.iterator(); it.hasNext(); )
        if (!predicate.evaluate(it.next()))
            it.remove();
}
...
filter(collection, new Predicate() {
    public boolean evaluate(Object o) { return whatever; }
});


Here is the Android way to implement a generic solution for this:

Usage:

Remove all null strings from my list

    LinkedList<String> list = ...
    ListUtils.filter(list, new ListUtils.Filter<String>() {
        @Override
        public boolean keepItem(String item) {
            return item != null;
        }
    });

Source:

public class ListUtils {
    public interface Filter<T>{
        boolean keepItem(T item);
    }

    public static <T> void filter(@NonNull List<T> items, @NonNull Filter<T> filter) {
        for (Iterator<T> iterator = items.iterator(); iterator.hasNext();){
            if(!filter.keepItem(iterator.next())){
                iterator.remove();
            }
        }
    }
}


See if lambdaj's filter option can help you.


You could always go backwards and delete the elements..

    for (int i = array.size() - 1; i >= 0; i--) {
       if (array.get(i).getCarColor() == Color.BLUE)
                array.remove(i);
    }

edit: Noticed it was a LinkedList which might make my answer a bit non-relevant.


I'm a big fan of the Iterator solution provided by Vivien Barousse and gpeche. But I wanted to point out that you don't have to actually remove any elements from the collection, you just need to prevent the filter from returning them. That way, you basically have multiple views of the same collection, which can be very convenient and efficient. The Filter object is basically your lamda expression, or as close as you're gonna get in Java until version 7...


It's really an old post but how abt using the way given in Oracle Java tutorial.

static void filter(Collection<?>c) {
    for (Iterator<?>it = c.iterator(); it.hasNext(); )
         if (!cond(it.next()))
             it.remove();
}


public static <T> void filter(List<T> list, Predicate<? super T> removeIf) {
    if(list == null) return;

    Iterator<T> iterator = list.iterator();
    while (iterator.hasNext()) {
        if (removeIf.apply(iterator.next())) iterator.remove();
    }
}

Pass a list of some generic type to this function with a predicate to remove unwanted elements.


With Java8 introducing lambda expressions, it's much easier to implement filtering on a collection in a more functional approach.

I wrote an example for you. Please also note how nicer it is to print Collection content using forEach:

public class Java8Filtering {

    public static void main(String[] argc) {
        LinkedList<Car> cars = new LinkedList<>();
        cars.add(new Car("car 1", Color.BLUE));
        cars.add(new Car("car 2", Color.GREEN));
        cars.add(new Car("car 3", Color.RED));
        cars.add(new Car("car 4", Color.BLUE));

        List<Car> filteredCars = cars.stream()
                .filter(car -> car.getCarColor() != Color.BLUE)
                .collect(Collectors.toList());

        filteredCars.forEach(car -> System.out.println("Car: " + car.getCarName() + " with color: " + car.getCarColor()));
    }
}

class Car {

    private Color carColor;
    private String carName;

    public Car(String carName, Color carColor) {
        this.carName = carName;
        this.carColor = carColor;
    }

    public Color getCarColor() {
        return carColor;
    }

    public void setCarColor(Color carColor) {
        this.carColor = carColor;
    }

    public String getCarName() {
        return carName;
    }

    public void setCarName(String carName) {
        this.carName = carName;
    }
}

enum Color {
    BLUE, GREEN, RED
}


For those of you who come across this thread and might be working on Android with RxJava/RxAndroid, there's a quick way to do this without adding the Apache Commons Collections dependency:

cars = Observable.from(cars).filter(car -> {
  if (car.getCarColor() == Color.BLUE) {
    return false;
  }

  return true;
}).toList().toBlocking().first();

Note that I also happen to be using lambda expressions with Retrolambda. If you aren't using Retrolambda, you can express the same thing using the following:

cars = Observable.from(cars).filter(new Func1<Car, Boolean>() {
      @Override
      public Boolean call(Car car) {
        if (car.getCarColor() == Color.BLUE) {
          return false;
        }

        return true;
      }
}).toList().toBlocking().first();
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜