我有一个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
}
}
并且代码应该能够根据这些标准动态生成过滤器。
假设你的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}
使用函数使其可重用。
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)
值得一提的是:Apache Commons BeanUtils库专门以动态方式访问bean属性。
请参阅BeanPropertyValueEqualsPredicate以获得理解。这只是平等匹配的解决方案。