Подтвердить что ты не робот

Легкий способ фильтрации элементов из коллекции на Java?

Я хочу написать метод, который удаляет все элементы из коллекции, которые следуют за определенным шаблоном. В функциональных языках я использовал бы фильтр() с выражением лямбда. Однако, на Java, кажется, я застрял в этом:

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

Удаление элементов напрямую вызывает исключение ConcurrentModificationException. Есть ли лучший способ сделать это, не прибегая к Google Collections?

4b9b3361

Ответ 1

Вы можете выполнить итерацию по списку с помощью ListIterator, который имеет remove.

Btw вы должны объявить свой список как List<Car> - программу для интерфейсов, а не для реализации.

Ответ 2

Возможно, вы могли бы использовать итераторы, которые немного эффективнее:

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

Кроме того, если вы хотите сделать это решение более универсальным, я бы предложил вам что-то вроде:

public interface Filter<T> {

    public boolean shouldRemove(T t);

}

И вы можете использовать его следующим образом:

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

Ваш метод вызывается следующим образом:

removeCars(new Filter<Car>() {

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

});

Ответ 3

С помощью Java 8 вы можете фильтровать с помощью выражения лямбда, используя Collection.removeIf.

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

Ответ 4

Вы можете использовать CollectionUtils.filter(). Он работает с Iterator, поэтому у него не должно быть проблем с удалением элементов непосредственно из Collection. Это еще одна зависимость. Если вы хотите, чтобы код был автономным, он был бы следующим:

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; }
});

Ответ 5

Посмотрите, может ли помочь вам опция lambdaj.

Ответ 6

Вы всегда можете вернуться назад и удалить элементы.

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

edit: Заметил, что это LinkedList, который может сделать мой ответ немного несущественным.

Ответ 7

Я большой поклонник решения Iterator, предоставленного Вивиен Барусс и gpeche. Но я хотел бы указать, что вам не нужно фактически удалять какие-либо элементы из коллекции, вам просто нужно, чтобы фильтр не возвращал их. Таким образом, у вас в основном есть несколько видов одной коллекции, что может быть очень удобным и эффективным. Объект Filter - это в основном ваше выражение lamda или как можно ближе к Java до версии 7...

Ответ 8

Это действительно старая статья, но как можно использовать способ, описанный в учебнике Oracle Java.

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

Ответ 9

Для тех из вас, кто сталкивается с этим потоком и может работать на Android с RxJava/RxAndroid, есть быстрый способ сделать это, не добавляя зависимость Apache Commons Collections:

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

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

Обратите внимание, что я также использую лямбда-выражения с Retrolambda. Если вы не используете Retrolambda, вы можете выразить одно и то же, используя следующее:

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();

Ответ 10

Вот способ Android для реализации общего решения для этого:

Использование:

Удалите все нулевые строки из моего списка

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

Источник:

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();
            }
        }
    }
}

Ответ 11

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();
    }
}

Передайте список некоторого общего типа этой функции с предикатом для удаления нежелательных элементов.

Ответ 12

С Java8, представляющим лямбда-выражения, гораздо проще реализовать фильтрацию в коллекции в более функциональном подходе.

Я написал вам пример. Также обратите внимание, насколько лучше печатать содержимое коллекции, используя 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
}