问题
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