我有一个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
}
}
,并且代码应能够根据这些条件动态生成过滤器。
答案 0 :(得分:1)
假设您的Cat
类遵循JavaBean约定,则可以根据属性名称使用java.beans.PropertyDescriptor
访问getter 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}
答案 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以获取理解。这只是平等匹配的解决方案。