Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sort a List of Objects based on runtime property

I have a List of Value Objects [VO]. These Objects have many properties and corresponding get/set methods. I want to sort this 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 conditional if block branching to create whichever new specific Comparator [anonymous inner Class] is needed (at the runtime). 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;
            }
        }
       });
like image 513
jijo Avatar asked Nov 26 '12 15:11

jijo


2 Answers

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);
        }
    }
});
like image 179
Bohemian Avatar answered Sep 24 '22 10:09

Bohemian


Do the following:

  • get the name of the field from the client
  • build the name of the getter -> "get" + field name (after capitalizing the first character)
  • try to find the method with reflection by using Class.getDeclaredMethod()
  • if found, invoke the returned Method object on two instances of your VO class
  • use the results of the invoked getter methods for sorting
like image 41
jahroy Avatar answered Sep 21 '22 10:09

jahroy