Search code examples
javaandroidstack-overflow

Sort List / Vector of any Object by any member's field?


This has been asked a thousand times, but all of answers I've seen do not work on a passed Object, they are always hard coded to which Object and which field. I'm looking for a way to sort a List / Vector of based on a string field. I don't mind if it uses Reflection or Voodoo magic.

The method I've written results in a StackOverFlowError (pun intended!).

I call my method as follows:

StandardComparator.sort("distance",Vector<?>)StaticItems.LocationList,Item.SingleLocation.class);

StandardComparator class is defined as such:

public class StandardComparator {   
    public static void sort(final String field, Vector<?> locationList, final Class typeOfObject){
        Collections.sort(locationList, new Comparator<Object>() {
            @Override
            public int compare(Object object1, Object object2) {
                try {
                    return this.compare(typeOfObject.getField(field),typeOfObject.getField(field));
                } catch (SecurityException e) {
                    e.printStackTrace();
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                }
                return 0;
            }
        });
    }
}

The error:

E/AndroidRuntime(22828): FATAL EXCEPTION: Thread-10
E/AndroidRuntime(22828): java.lang.StackOverflowError
E/AndroidRuntime(22828):        at java.lang.reflect.Field.<init>(Field.java:89)
E/AndroidRuntime(22828):        at java.lang.reflect.Field.<init>(Field.java:81)
E/AndroidRuntime(22828):        at java.lang.reflect.ReflectionAccessImpl.clone(ReflectionAccessImpl.java:42)
E/AndroidRuntime(22828):        at java.lang.Class.getField(Class.java:870)
E/AndroidRuntime(22828):        at com.AtClass.Extras.StandardComparator$1.compare(StandardComparator.java:24)

A SingleLocation object:

public class SingleLocation {
        int id;
        public String deviceId;
        public String title;
        public String message;
        public double latCoords;
        public double lngCoords;
        public String locSeen;
        public Bitmap icon;
        public double distance;
        public String distanceString;

        public SingleLocation(String id, String deviceId, String title, String message, String latCoords, String lngCoords, String locSeen){
            this.id = Integer.valueOf(id);
            this.deviceId = deviceId;
            this.title = title;
            this.message = message;
            this.latCoords = Double.valueOf(latCoords);
            this.lngCoords = Double.valueOf(lngCoords);
            this.locSeen = locSeen;
        }
    }

Solution

  • Here's an example that will work on any Comparable field. You'd have to add special handling for primitive types and non-Comparables:

    import java.lang.reflect.Field;
    import java.util.*;
    
    public class ReflectionBasedComparator {
        public static void main(String[] args) {
            List<Foo> foos = Arrays.asList(new Foo("a", "z"), new Foo("z", "a"), new Foo("n", "n"));
            Collections.sort(foos, new ReflectiveComparator("s"));
            System.out.println(foos);
            Collections.sort(foos, new ReflectiveComparator("t"));
            System.out.println(foos);
        }
    
        static class Foo {
            private String s;
            private String t;
    
            public Foo(String s, String t) {
                this.s = s;
                this.t = t;
            }
    
            @Override
            public String toString() {
                return "Foo{" +
                               "s='" + s + '\'' +
                               ", t='" + t + '\'' +
                               '}';
            }
        }
    
        private static class ReflectiveComparator implements Comparator<Object> {
            private String fieldName;
    
            public ReflectiveComparator(String fieldName) {
                this.fieldName = fieldName;
            }
    
            @Override
            public int compare(Object o1, Object o2) {
                try {
                    Field field = o1.getClass().getDeclaredField(fieldName);
                    if (!Comparable.class.isAssignableFrom(field.getType())) {
                        System.out.println(field.getType());
                        throw new IllegalStateException("Field not Comparable: " + field);
                    }
                    field.setAccessible(true);
                    Comparable o1FieldValue = (Comparable) field.get(o1);
                    Comparable o2FieldValue = (Comparable) field.get(o2);
                    return o1FieldValue.compareTo(o2FieldValue);
                } catch (NoSuchFieldException e) {
                    throw new IllegalStateException("Field doesn't exist", e);
                } catch (IllegalAccessException e) {
                    throw new IllegalStateException("Field inaccessible", e);
                }
            }
        }
    }