我如何在运行时过滤我不知道的变量的Java对象列表?

时间:2019-04-28 15:34:33

标签: java java-8 java-stream predicate

我有一个Java对象列表,我想在运行时使用流来过滤它们。但是,仅在运行时才知道要过滤的变量。

例如,用户说我想要一张毛皮长度超过3厘米的所有猫的清单,我应该可以

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

但是应该动态调用getFurLength()getter-如果用户改为按眼睛颜色过滤,则我应该能够调用

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

如何在不事先编写所有可能的过滤器的情况下实现这一目标?

理想情况下,用户应发送以下内容:

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

,并且代码应能够根据这些条件动态生成过滤器。

3 个答案:

答案 0 :(得分:1)

假设您的Cat类遵循JavaBean约定,则可以根据属性名称使用java.beans.PropertyDescriptor访问getter Method

这使我们能够了解正在处理的价值类型。如果是数字,我们可以处理greater_than和其他运算符,但是如果它是非数字,我们应该只处理equals_to运算符。

“简化”且非常有限的解决方案如下所示:

注意:
-解决方案不支持诸如int之类的原始数字类型。请改用IntegerDouble等。
-我将所有数字都转换为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}

答案 1 :(得分:0)

通过功能使其可重用。

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)

答案 2 :(得分:0)

其价值:Apache Commons BeanUtils库专门用于以动态方式访问bean属性。

请参阅BeanPropertyValueEqualsPredicate以获取理解。这只是平等匹配的解决方案。