Как отфильтровать список java-объектов во время выполнения, когда переменная, по которой я фильтрую, заранее неизвестна? - PullRequest
1 голос
/ 28 апреля 2019

У меня есть список объектов Java, и я хочу использовать поток для их фильтрации во время выполнения. Однако переменная, по которой я хочу фильтровать, известна только во время выполнения.

Например, пользователь говорит, что я хочу получить список всех кошек, у которых длина меха превышает 3 см, я должен быть в состоянии сделать

cats.stream().filter(cat -> cat.getFurLength() > 3).collect(Collectors.toList());

Однако метод getFurLength () должен вызываться динамически - если вместо этого пользователь хочет выполнить фильтрацию по цвету глаза, я смогу вызвать

cats.stream().filter(cat -> cat.getEyeColour() == Colour.BLUE).collect(Collectors.toList());

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

В идеале пользователь должен отправить что-то вроде:

{
  eyeColour:{
    operator: "equal_to",
    value: "BLUE"
  },
  furLength: {
    operator: "greater_than",
    value: 3
  }
}

и код должен иметь возможность динамически генерировать фильтры на основе этих критериев.

Ответы [ 3 ]

1 голос
/ 28 апреля 2019

Если ваш класс Cat соответствует соглашению JavaBean, вы можете использовать java.beans.PropertyDescriptor для доступа к получателю Method на основе имени свойства.

Это позволяет нам узнать, с каким типом ценности мы имеем дело. Если он числовой, мы можем обрабатывать greater_than и другие операторы, но если он не числовой, мы должны обрабатывать только оператор equals_to.

«Упрощенное» и очень ограниченное решение может выглядеть так:

Примечание:
- Решение не поддерживает примитивные числовые типы, такие как int. Вместо этого используйте Integer, Double и т. Д.
- Я конвертирую все числа в BigDecimal и использую compareTo для упрощения сравнения числовых типов, если вы получаете какие-либо ошибки для больших чисел или очень точные, не стесняйтесь заменить их на правильное сравнение типов).
- для проверки на равенство сравнивает строковое представление объектов (результат toString()), поэтому для Color вы не можете использовать BLUE, но ваш JSON должен содержать java.awt.Color[r=0,g=0,b=255])

class PredicatesUtil {

    static <T> Predicate<T> filters(Class<T> clazz, String filtersJson) {

        JSONObject jsonObject = new JSONObject(filtersJson);

        List<Predicate<T>> predicateList = new ArrayList<>();
        for (String property : jsonObject.keySet()) {
            JSONObject filterSettings = jsonObject.getJSONObject(property);

            try {
                String operator = filterSettings.getString("operator");
                String value = filterSettings.getString("value");
                predicateList.add(propertyPredicate(clazz, property, operator, value));
            } catch (IntrospectionException e) {
                throw new RuntimeException(e);
            }
        }
        return combinePredicatesUsingAND(predicateList);
    }

    static <T> Predicate<T> combinePredicatesUsingAND(List<Predicate<T>> predicateList) {
        return t -> {
            for (Predicate<T> pr : predicateList) {
                if (!pr.test(t))
                    return false;
            }
            return true;
        };
    }

    static <T> Predicate<T> propertyPredicate(Class<T> clazz, String property,
                                              String operator, String value)
            throws IntrospectionException {

        final Method m = new PropertyDescriptor(property, clazz).getReadMethod();
        final Class<?> returnType = m.getReturnType();

        return obj -> {
            try {
                Object getterValue = m.invoke(obj);
                if (Number.class.isAssignableFrom(returnType)) {
                    BigDecimal getValue = new BigDecimal(getterValue.toString());
                    BigDecimal numValue = new BigDecimal(value);

                    int compared = getValue.compareTo(numValue);
                    if (operator.equalsIgnoreCase("equal_to")) {
                        return compared == 0;
                    } else if (operator.equalsIgnoreCase("lesser_than")) {
                        return compared < 0;
                    } else if (operator.equalsIgnoreCase("greater_than")) {
                        return compared > 0;
                    } else {
                        throw new RuntimeException("not recognized operator for numeric type: " + operator);
                    }
                } else {
                    //System.out.println("testing non-numeric, only euals_to");
                    if (operator.equalsIgnoreCase("equal_to")) {
                        return value.equalsIgnoreCase(getterValue.toString());
                    }
                    throw new RuntimeException("not recognized operator: " + operator);
                }
            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        };
    }
}

, который можно использовать как:

class Cat {
    private Color eyeColour;
    private Integer furLength;

    Cat(Color eyeColor, Integer furLength) {
        this.eyeColour = eyeColor;
        this.furLength = furLength;
    }

    public Color getEyeColour() {
        return eyeColour;
    }

    public Integer getFurLength() {
        return furLength;
    }

    public void setEyeColour(Color eyeColour) {
        this.eyeColour = eyeColour;
    }

    public void setFurLength(Integer furLength) {
        this.furLength = furLength;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "eyeColor=" + eyeColour +
                ", furLength=" + furLength +
                '}';
    }
}
class CatsDemo {
    public static void main(String[] args) {

        String json = 
                "{\n" +
                "  eyeColour:{\n" +
                "    operator: \"equal_to\",\n" +
                "    value: \"java.awt.Color[r=0,g=0,b=255]\"\n" +
                "  },\n" +
                "  furLength: {\n" +
                "    operator: \"greater_than\",\n" +
                "    value: \"3\"\n" +
                "  }\n" +
                "}";

        List<Cat> cats = List.of(
                new Cat(Color.blue, 1),
                new Cat(Color.blue, 2),
                new Cat(Color.blue, 3),
                new Cat(Color.blue, 4),
                new Cat(Color.blue, 5),
                new Cat(Color.yellow, 1),
                new Cat(Color.yellow, 2),
                new Cat(Color.yellow, 3),
                new Cat(Color.yellow, 4),
                new Cat(Color.yellow, 5)
        );

        cats.stream()
            .filter(PredicatesUtil.filters(Cat.class, json))
            .forEach(System.out::println);
    }
}

Выход:

Cat{eyeColor=java.awt.Color[r=0,g=0,b=255], furLength=4}
Cat{eyeColor=java.awt.Color[r=0,g=0,b=255], furLength=5}
0 голосов
/ 28 апреля 2019

Для чего стоит: Библиотека Apache Commons BeanUtils специализируется на динамическом доступе к свойствам бина.

См. BeanPropertyValueEqualsPredicate для понимания.Это только решение для матчей на равенство.

0 голосов
/ 28 апреля 2019

Сделайте его многоразовым с функцией.

List<Cat> filterCats(cats, Predicate<Cat> filter) {
    return cats.stream().filter(filter).collect(Collectors.toList());
}

А затем используйте его с:

filterCats(cats, cat -> cat.getEyeColour() == Colour.BLUE)

Или,

filterCats(cats, cat -> cat.getFurLength() > 3)
...