Kevin Parker
Kevin Parker

Reputation: 17206

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

Upvotes: 0

Views: 1193

Answers (4)

camickr
camickr

Reputation: 324147

The Bean Comparator should work.

Upvotes: 0

Ryan Stewart
Ryan Stewart

Reputation: 128899

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

Upvotes: 1

n0rmzzz
n0rmzzz

Reputation: 3848

You can replace your compare method with this:

return typeOfObject.getField(field).get(object1).compareTo(typeOfObject.getField(field).get(object2));

and cater for exceptions (field not found, null pointer, ...).

Upvotes: 0

Nikolay Elenkov
Nikolay Elenkov

Reputation: 52956

You are just calling your own method, causing the stack overflow. Since you know the fields are String's, just cast them to String and use its compareTo() method.

Upvotes: 1

Related Questions