vikingsteve
vikingsteve

Reputation: 40398

Is there a nice way to implement multiple comparators via an enum?

Imagine that I have a domain object with an enum named SortBy to facilitate sorting of the data from the service and / or dao layer.

public class Item {
    public static enum SortBy { CATEGORY, NORWEGIAN, ENGLISH; }

    private String category;
    private String norwegian;
    private String English;
}

This sort of operation is therefore possible: itemDao.getItems(Item.SortBy.CATEGORY)

Now, is there a design pattern or nice way to relate the SortBy values to java Comparator's?

I am asking this since the ways I initially thought about used static, and that's bad practise (right?).

  1. A Map<SortBy, Comparator<Item> in the Item class?

  2. A class ItemComparators with method getComparator(Item.SortBy sort)?

  3. A method on the SortBy enum itself that returns a static instance of Comparator<Item>?

I'm looking for a minimalist way to do this, without going so far as to have a ItemComparatorProvider bean.

Appreciate any information...

Upvotes: 1

Views: 1235

Answers (3)

Mike Strobel
Mike Strobel

Reputation: 25623

The most straightforward way to do this is to simply have the enum itself implement Comparator:

public enum SortBy implements Comparator<Item> {
    CATEGORY {
        @Override
        public final int compare(final Item o1, final Item o2) {
            return compareStrings(o1.getCategory(), o2.getCategory());
        }
    },
    ENGLISH {
        @Override
        public final int compare(final Item o1, final Item o2) {
            return compareStrings(o1.getEnglish(), o2.getEnglish());
        }
    },
    NORWEGIAN {
        @Override
        public final int compare(final Item o1, final Item o2) {
            return compareStrings(o1.getNorwegian(), o2.getNorwegian());
        }
    };

    private static int compareStrings(final String s1, final String s2) {
        if (s1 == null) {
            return s2 == null ? 0 : -1;
        }
        if (s2 == null) {
            return 1;
        }
        return s1.compareTo(s2);
    }
}

There's no need to further associate each enum member with a comparator because the enum members are the comparators. The usage is then nice and concise:

Collections.sort(items, SortBy.CATEGORY);

Addendum

In the comments below, you asked about common, null-safe comparisons. I'm not sure what third-party libraries might provide them, but you can implement them yourself easily enough. Here's two from one of our internal libraries:

static <T extends Comparable<? super T>> int compare(final T o1, final T o2) {
    if (o1 == null) {
        return o2 == null ? 0 : -1;
    }
    if (o2 == null) {
        return 1;
    }
    return o1.compareTo(o2);
}

@SuppressWarnings("unchecked")
static int compare(final Object a, final Object b) {
    if (a == b) {
        return 0;
    }

    if (a == null) {
        return -1;
    }

    if (b == null) {
        return 1;
    }

    final Class<?> aClass = a.getClass();
    final Class<?> bClass = b.getClass();

    if (Comparable.class.isInstance(a) && aClass.isAssignableFrom(bClass)) {
        return ((Comparable<Object>)a).compareTo(b);
    }

    if (Comparable.class.isInstance(b) && bClass.isAssignableFrom(aClass)) {
        return ((Comparable<Object>)b).compareTo(a);
    }

    throw new IllegalArgumentException("Values must be comparable.");
}

Upvotes: 4

Adam Arold
Adam Arold

Reputation: 30538

You can think about something like this:

public static enum SortBy {

 CATEGORY(new CategoryComparator()), NORWEGIAN(new NorwegianComparator()), ENGLISH(new EnglishComparator());

    Comparator<Item> comparator;

    private SortBy(Comparator<Item> comparator) {
        this.comparator = comparator;
    }

}

You can either implement Comparator and create new classes if you will use them elsewhere or implement them as anonymous inner classes like in vikingsteve's answer.

Upvotes: 2

vikingsteve
vikingsteve

Reputation: 40398

Ok well here is one way to do it. Is this fine?

public static enum SortBy {
    CATEGORY(new Comparator<Item>() {
        @Override
        public int compare(Item o1, Item o2) {
            return o1.getCategory().compareTo(o2.getCategory());
        }
    }),
    NORWEGIAN(new Comparator<Item>() {
        @Override
        public int compare(Item o1, Item o2) {
            return o1.getNorwegian().compareTo(o2.getNorwegian());
        }
    }),
    ENGLISH(new Comparator<Item>() {
        @Override
        public int compare(Item o1, Item o2) {
            return o1.getEnglish().compareTo(o2.getEnglish());
        }
    });
    private Comparator<Item> comparator;

    private SortBy(Comparator<Item> comparator) {
        this.comparator = comparator;
    }

    public Comparator<Item> getComparator() {
        return comparator;
    }
}

Upvotes: 1

Related Questions