How can we pass a variable field /method name in Comparator.comparing

柔情痞子 提交于 2020-06-11 08:11:08

问题


I have a Report {String name, Date date, int score } class. I want to be able to sort a list of reports for any member variable using the new java 8 syntax

So java 8 provides this new

list.sort(Comparator.comparing(report -> report.name)) 

to sort the list on name.

Lets say instead of name I want to provide a variable field name to this method eg. something like

list.sort(Comparator.comparing(report -> report.anyField))

where anyField can be name or date or score. How do I achieve this behavior.


回答1:


One very generic solution is to use Java's Reflection and some casting:

    String sortBy = "name";
    list.sort(Comparator.comparing(report -> {
        try {
            return (Comparable) report.getClass().getDeclaredField(sortBy).get(report);
        } catch (Exception e) {
            throw new RuntimeException("Ooops", e);
        }
    }));    

If you use an additional library like https://github.com/jOOQ/jOOR the code becomes even simpler:

    String sortBy = "score";
    list.sort(Comparator.comparing(report -> Reflect.on(report).field(sortBy).get()));

Please be aware that this solution only works with fields that implement Comparable and that it has some runtime overhead.




回答2:


Just create a comparator for each property.

static Map<String,Comparator<Report>> ORDER;
static {
    HashMap<String,Comparator<Report>> m=new HashMap<>();
    m.put("name", Comparator.comparing(r -> r.name));
    m.put("date", Comparator.comparing(r -> r.date));
    m.put("score", Comparator.comparingInt(r -> r.score));
    ORDER=Collections.unmodifiableMap(m);
}
public static void sort(List<Report> list, String order) {
    Comparator<Report> c=ORDER.get(order);
    if(c==null) throw new IllegalArgumentException(order);
    list.sort(c);
}

You may consider using an enum as alternative to String, which eliminates the possibility of providing a non-existent property name:

enum ReportOrder {
    NAME(Comparator.comparing(r -> r.name)),
    DATE(Comparator.comparing(r -> r.date)),
    SCORE(Comparator.comparingInt(r -> r.score));

    private Comparator<Report> cmp;
    private ReportOrder(Comparator<Report> c) { cmp=c; }

    public void sort(List<Report> list) {
        list.sort(cmp);
    }
}

Now you can just say, e.g. ReportOrder.NAME.sort(list);. Of course, the other delegation style works as well:

public static void sort(List<Report> list, ReportOrder o) {
    list.sort(o.cmp);
}

 

sort(list, ReportOrder.DATE);



回答3:


public class Report {

    //properties and getters

    public static void sort(List<Report> list, Function<Report, Comparable> sortKey) {
        list.sort(Comparator.comparing(sortKey));
    }
}

Report.sort(reports, Report::getName);
Report.sort(reports, Report::getDate);
Report.sort(reports, Report::getScore);

Could make this into a generic util class where you can pass in a List of any class:

public class MyUtil<T> {
    void sort(List<T> t, Function<T, Comparable> sortKey) {
        t.sort(Comparator.comparing(sortKey));
    }
}

MyUtil<Report> util = new MyUtil();
util.sort(ppl, Report::getName);



回答4:


If your Report has getter method of various field you can do like this

list.sort(Comparator.comparing(Report::getFieldName));

Or with lambda expression

list.sort((ob1, ob2) -> ob1.getFieldName().compareTo(ob2.getFieldName()));



回答5:


At some place, you'll have to use a bit of reflection to pull this off. Here's an example using the bean introspection mechanism of java:

    public static class MyBean {
        private String  name;
        private Date    birthday;
        private Integer score;
        ...
        ... (constructor, getters, setters - the usual bean stuff)
    }

    PropertyDescriptor[] pdesc = Introspector.getBeanInfo(MyBean.class).getPropertyDescriptors();
    for(PropertyDescriptor pd : pdesc) {
        if(Comparable.class.isAssignableFrom(pd.getPropertyType())) {
            System.out.println("Property " + pd.getName() + " could be used for comparison");

            Function<MyBean, Comparable> extractor = b -> {
                try {
                    return (Comparable) pd.getReadMethod().invoke(b);
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            };

            Comparator<MyBean> comp = Comparator.comparing(extractor);

            // do something useful with the comparator :-)
        }
    }

Additionally, you could go a similar way for primitive types (e.g. int, which does not implement Comparable as opposed to Integer.)




回答6:


If the set of properties is fixed (name, date, score) then I think a clean way would be to pass a Comparator:

private void someMethod(Comparator<Report> comparator){
   ...
   list.sort(comparator);
   ...
}

...

someMethod(Comparator.comparing(report::getName));
someMethod(Comparator.comparing(report::getDate));
someMethod(Comparator.comparingInt(report::getScore));
someMethod(Comparator.comparing(report::getName).thenComparingInt(report::getScore));



回答7:


There is a Comparator class called NamedMethodComparator that will work as a Comparator for any zero arg method that returns a Comparable posted here: How to use Comparator in Java to sort



来源:https://stackoverflow.com/questions/39463175/how-can-we-pass-a-variable-field-method-name-in-comparator-comparing

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!