sort a list of objects based on runtime property

2020-02-26 01:53发布

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;
            }
        }
       });

3条回答
唯我独甜
2楼-- · 2020-02-26 02:41

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);
        }
    }
});
查看更多
Fickle 薄情
3楼-- · 2020-02-26 02:42

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
查看更多
贪生不怕死
4楼-- · 2020-02-26 02:48

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.

查看更多
登录 后发表回答