Как применить несколько фильтров на Java Stream? - PullRequest
0 голосов
/ 05 июля 2018

Мне нужно отфильтровать коллекцию объектов по карте, которая содержит пары ключевых значений имен полей объектов и значений полей. Я пытаюсь применить все фильтры по stream (). Filter ().

Объекты на самом деле являются JSON, поэтому карта содержит имена ее переменных, а также значение, которое они должны содержать, чтобы быть принятыми, но по причинам простоты и потому, что это не относится к вопросу, я написал простой Testclass для моделирования поведения:

public class TestObject {

  private int property1;
  private int property2;
  private int property3;

  public TestObject(int property1, int property2, int property3) {
      this.property1 = property1;
      this.property2 = property2;
      this.property3 = property3;
  }

  public int getProperty(int key) {
      switch(key) {
          case 1: return property1;
          case 2: return property2;
          default: return property3;
      }
  }
}

Что я пробовал до сих пор:

public static void main(String[] args) {
    List<TestObject> list = new ArrayList<>();
    Map<Integer, Integer> filterMap = new HashMap<>();
    list.add(new TestObject(1, 2, 3));
    list.add(new TestObject(1, 2, 4));
    list.add(new TestObject(1, 4, 3));
    filterMap.put(3, 3); //Filter property3 == 3
    filterMap.put(2, 2); //Filter property2 == 2

    //Does not apply the result
    filterMap.forEach((key, value) -> list.stream()
            .filter(testObject -> testObject.getProperty(key) == value)
            .collect(Collectors.toList())
    );
    /* Gives error: boolean can not be converted to void
    list = list.stream()
            .filter(testObject -> filterMap.forEach((key, value) -> testObject.getProperty(key) == value))
            .collect(Collectors.toList()
            );
    */
    //Printing result

    list.forEach(obj -> System.out.println(obj.getProperty(1) + " " + obj.getProperty(2) + " " + obj.getProperty(3)));
}

Я попытался сначала указать forEach of the Map, а затем поток Collection, но оба решения не работали должным образом. Желаемым результатом этого примера будет только печать объекта со значениями property1 = 1, property2 = 2 и property3 = 3.

Как правильно применить все фильтры, как если бы вы вставляли их один за другим в код с фиксированным количеством фильтров?

С известным количеством фильтров:

list.stream().filter(...).filter(...)

Edit:

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

Ответы [ 4 ]

0 голосов
/ 05 июля 2018

Более общий подход заключается в создании мульти фильтра (Predicate), который объединяется с использованием Predicate.and(...) или Predicate.or(...). Это применимо ко всему, что использует Predicate - прежде всего Stream и Optional.
Поскольку результатом является Predicate, можно продолжить с Predicate.and(...) или Predicate.or(...) или с созданием более сложных предикатов, используя MultiPredicate снова.

class MultiPredicate {

    public static <T> Predicate<T> matchingAll(Collection<Predicate<T>> predicates) {
        Predicate<T> multiPredicate = to -> true;
        for (Predicate<T> predicate : predicates) {
            multiPredicate = multiPredicate.and(predicate);
        }

        return multiPredicate;    
    }

    @SafeVarargs
    public static <T> Predicate<T> matchingAll(Predicate<T> first, Predicate<T>... other) {
        if (other == null || other.length == 0) {
            return first;
        }

        Predicate<T> multiPredicate = first;
        for (Predicate<T> predicate : other) {
            multiPredicate = multiPredicate.and(predicate);
        }

        return multiPredicate;
    }

    public static <T> Predicate<T> matchingAny(Collection<Predicate<T>> predicates) {
        Predicate<T> multiPredicate = to -> false;
        for (Predicate<T> predicate : predicates) {
            multiPredicate = multiPredicate.or(predicate);
        }

        return multiPredicate;    
    }

    @SafeVarargs
    public static <T> Predicate<T> matchingAny(Predicate<T> first, Predicate<T>... other) {
        if (other == null || other.length == 0) {
            return first;
        }

        Predicate<T> multiPredicate = first;
        for (Predicate<T> predicate : other) {
            multiPredicate = multiPredicate.or(predicate);
        }

        return multiPredicate;
    }

}

Применительно к вопросу:

public static void main(String... args) {
    List<TestObject> list = new ArrayList<>();
    Map<Integer, Integer> filterMap = new HashMap<>();
    list.add(new TestObject(1, 2, 3));
    list.add(new TestObject(1, 2, 4));
    list.add(new TestObject(1, 4, 3));
    filterMap.put(3, 3); // Filter property3 == 3
    filterMap.put(2, 2); // Filter property2 == 2

    List<Predicate<TestObject>> filters = filterMap.entrySet().stream()
            .map(filterMapEntry -> mapToFilter(filterMapEntry))
            .collect(Collectors.toList());

    Predicate<TestObject> multiFilter = MultiPredicate.matchingAll(filters);
    List<TestObject> filtered = list.stream()
            .filter(multiFilter)
            .collect(Collectors.toList());
    for (TestObject to : filtered) {
        System.out.println("(" + to.getProperty(1) + "|" + to.getProperty(2) + "|" + to.getProperty(3) + ")");
    }

}

private static Predicate<TestObject> mapToFilter(Entry<Integer,Integer> filterMapEntry) {
    return to -> to.getProperty(filterMapEntry.getKey()) ==  filterMapEntry.getValue();
}

В этом случае все фильтры должны совпадать. Результат:

(1|2|3)  

Если мы используем MultiPredicate.matchingAny(...), результат будет:

(1|2|3)  
(1|2|4)  
(1|4|3)  
0 голосов
/ 05 июля 2018

Чтобы применить переменное число шагов фильтра к потоку (которые становятся известны только во время выполнения), вы можете использовать цикл для добавления шагов фильтра.

Stream<TestObject> stream = list.stream();
for (Predicate<TestObject> predicate: allPredicates) {
  stream = stream.filter(predicate);
}
list = stream.collect(Collectors.toList());
0 голосов
/ 05 июля 2018

Это не имеет ничего общего с фильтром. На самом деле фильтр никогда не работает в соответствии с вашим кодом. Посмотрите на

//Does not apply the result
filterMap.forEach((key, value) -> list.stream()
        .filter(testObject -> testObject.getProperty(key) == value)
        .collect(Collectors.toList())
);

Список был отфильтрован, но здесь ничего не изменилось. Ни один элемент не был удален и адрес объекта также не был изменен. Попробуйте removeIf

// Does not apply the result
filterMap.forEach((key, value) -> list.removeIf(testObject -> testObject.getProperty(key) != value));

вывод

1 2 3
0 голосов
/ 05 июля 2018

Полагаю, вы хотите сохранить все TestObjects, которые удовлетворяют всем условиям, указанным на карте?

Это сделает работу:

List<TestObject> newList = list.stream()
        .filter(x ->
                filterMap.entrySet().stream()
                        .allMatch(y ->
                                x.getProperty(y.getKey()) == y.getValue()
                        )
        )
        .collect(Collectors.toList());

Перевод на "английский",

filter список list, сохранив все элементы x, которые:

  • все пары ключ-значение y из filterMap должны удовлетворять:
    • x.getProperty(y.getKey()) == y.getValue()

(Не думаю, что я сделал хорошую работу, чтобы сделать этого человека читабельным ...) Если вы хотите более удобочитаемое решение, я рекомендую ответ Йеруна Стинбика.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...