根据对象的参数化属性对对象列表进行排序

时间:2017-08-07 15:48:00

标签: java-8

假设我们有一个具有以下属性的对象:

public class MyObject {

    private String attr1;
    private Integer attr2;

    //...

    public String getAttr1() {
        return this.attr1;
    }

    public Integer getAttr2() {
        return this.attr2;
    }
}

根据其属性mylist对此对象的列表attr1进行排序的一种方法是:

mylist.sort(Comparator.comparing(MyObject::getAttr1));

是否可以以动态方式在方法中使用此代码,并使用根据其名称返回对象属性的getter的方法替换getAttr1部分?类似的东西:

public void sortListByAttr(List<MyObject> list, String attr) {
    list.sort(Comparator.comparing(MyObject::getGetterByAttr(attr)));
}

MyObject::getGetterByAttr(attr)部分无法编译,我只是作为一个例子来解释我的想法

我尝试使用以下代码new PropertyDescriptor(attr, MyObject.class).getReadMethod().invoke(new MyObject())实现一个方法但是仍然无法使用comparing方法中的参数调用方法

2 个答案:

答案 0 :(得分:6)

你可以添加像

这样的方法
public static Function<MyObject,Object> getGetterByAttr(String s) {
    switch(s) {
        case "attr1": return MyObject::getAttr1;
        case "attr2": return MyObject::getAttr2;
    }
    throw new IllegalArgumentException(s);
}

到您的班级,但返回的函数不适合Comparator.comparing,因为它要求某个类型符合U extends Comparable<? super U>,而StringInteger的每个类型都能够在单个调用中实现此约束时,无法为getGetterByAttr声明通用返回类型以允许两种类型并且仍然与comparing的声明兼容。

替代方案是完整Comparator的工厂。

public static Comparator<MyObject> getComparator(String s) {
    switch(s) {
        case "attr1": return Comparator.comparing(MyObject::getAttr1);
        case "attr2": return Comparator.comparing(MyObject::getAttr2);
    }
    throw new IllegalArgumentException(s);
}

一样使用
public void sortListByAttr(List<MyObject> list, String attr) {
    list.sort(getComparator(attr));
}

这样做的好处是它还可以支持类型不是Comparable且需要自定义Comparator的属性。此外,原始类型的更有效的比较器(例如使用comparingInt)也是可能的。

您也可以考虑使用Map代替switch

private static Map<String,Comparator<MyObject>> COMPARATORS;
static {
    Map<String,Comparator<MyObject>> comparators=new HashMap<>();
    comparators.put("attr1", Comparator.comparing(MyObject::getAttr1));
    comparators.put("attr2", Comparator.comparing(MyObject::getAttr2));
    COMPARATORS = Collections.unmodifiableMap(comparators);
}
public static Comparator<MyObject> getComparator(String s) {
    Comparator<MyObject> comparator = COMPARATORS.get(s);
    if(comparator != null) return comparator;
    throw new IllegalArgumentException(s);
}

只有通过Reflection才能实现更多的动态,但这会使代码复杂化,添加大量潜在的错误源,只有很少的好处,考虑到你只需要添加一行源代码来添加对另一个属性的支持上面的例子中的任何一个。毕竟,定义的属性集在编译时得到修复。

答案 1 :(得分:3)

您也可以在一个地方定义这些比较器:

 static enum MyObjectComparator {

    ATTR1("attr1", Comparator.comparing(MyObject::getAttr1));

    MyObjectComparator(String attrName, Comparator<MyObject> comparator) {
        this.comparator = comparator;
        this.attrName = attrName;
    }

    private final Comparator<MyObject> comparator;

    private final String attrName;

    private static MyObjectComparator[] allValues = MyObjectComparator.values();

    public static Comparator<MyObject> findByValue(String value) {
        return Arrays.stream(allValues)
                .filter(x -> x.attrName.equalsIgnoreCase(value))
                .map(x -> x.comparator)
                .findAny()
                .orElseThrow(RuntimeException::new);
    }

}

你的用法是:

public void sortListByAttr(List<MyObject> list, String attr) {
    list.sort(MyObjectComparator.findByValue(attr));
}