I have an arraylist of VOs. These objects have many properties and corresponding get/set methods. I want to sort this array list based on a property which I'll be getting in runtime.
Let me explain in detail. My VO is like this
public class Employee {
String name;
String id;
private String getName() {
return name;
}
private String getId() {
return id;
}
}
I will be getting a string ‘sortType’ in runtime, which can be either ‘id’ or ‘name’. I want to sort the list based on the value of the string.
I have tried to use comparator and reflection together, but no luck. May be I didn't use it properly.I don’t want to use an if loop and create new comparator classes. Any other thoughts?
The try catch should be inside the new class. Here is the working code. If you want to use a separate class for comparator, please find it in @Bohemian's comment below.
String sortType = "name"; // determined at runtime
Collections.sort(results, new Comparator<Employee>() {
public int compare(Employee c1, Employee c2) {
try{
Method m = c1.getClass().getMethod("get" + StringUtils.capitalize(sortType));
String s1 = (String)m.invoke(c1);
String s2 = (String)m.invoke(c2);
return s1.compareTo(s2);
}
catch (Exception e) {
return 0;
}
}
});
Create a Comparator
for the job:
public class EmployeeComparator implements Comparator<Employee> {
private final String type;
public EmployeeComparator (String type) {
this.type = type;
}
public int compare(Employee e1, Employee e2) {
if (type.equals("name")) {
return e1.getName().compareTo(e2.getName());
}
return e1.getId().compareTo(e2.getId());
}
}
Then to use it
String type = "name"; // determined at runtime
Collections.sort(list, new EmployeeComparator(type));
The reflective version would be similar, except you would look for a method on the object of "get" + type (capitalised) and invoke that and hard cast it to Comparable and use compareTo (I'll try to show the code, but I'm using my iPhone and its a bit of a stretch, but here goes)
public class DynamicComparator implements Comparator<Object> {
private final String type;
// pass in type capitalised, eg "Name"
// ie the getter method name minus the "get"
public DynamicComparator (String type) {
this.type = type;
}
public int compare(Object o1, Object o2) {
// try-catch omitted
Method m = o1.getClass().getMethod("get" + type);
String s1 = (String)m.invoke(o1);
String s2 = (String)m.invoke(o2);
return s1.compareTo(s2);
}
}
OK... Here's how to do it without creating a class, using an anonymous class (with exception handling so code compiles):
List<?> list;
final String attribute = "Name"; // for example. Also, this is case-sensitive
Collections.sort(list, new Comparator<Object>() {
public int compare(Object o1, Object o2) {
try {
Method m = o1.getClass().getMethod("get" + attribute);
// Assume String type. If different, you must handle each type
String s1 = (String) m.invoke(o1);
String s2 = (String) m.invoke(o2);
return s1.compareTo(s2);
// simply re-throw checked exceptions wrapped in an unchecked exception
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
});
Keep it simple!
If the choice is only id
or name
—use an if statement.
Picking between two choices. That’s what if has been invented for.
Or, if it is many properties, then use reflection, or store the data in a Map
in the first place. Sometimes Map
is better than an class. In particular if your VO has not methods other than getters and setters.
Caution though, using reflection is might be unsafe in this case as your client might inject any term in the CGI parameters in an attack akin to SQL injection.