4

Is there a way to make this method properly generic and do away with the warnings?

/**
 * <p>Sort a collection by a certain "value" in its entries. This value is retrieved using
 * the given <code>valueFunction</code> which takes an entry as argument and returns
 * its value.</p>
 * 
 * <p>Example:</p>
 * <pre>// sort tiles by number
 *Collects.sortByValue(tileList, true, new Function<Integer,NormalTile>() {
 *  public Integer call(NormalTile t) {
 *      return t.getNumber();
 *  }
 *});</pre>
 *
 * @param list The collection.
 * @param ascending Whether to sort ascending (<code>true</code>) or descending (<code>false</code>).
 * @param valueFunction The function that retrieves the value of an entry.
 */
public static <T> void sortByValue(List<T> list, final boolean ascending, @SuppressWarnings("rawtypes") final Function<? extends Comparable, T> valueFunction) {
    Collections.sort(list, new Comparator<T>() {
        @SuppressWarnings({ "unchecked", "rawtypes" })
        @Override public int compare(T o1, T o2) {
            final Comparable v1 = valueFunction.call(o1);
            final Comparable v2 = valueFunction.call(o2);
            return v1.compareTo(v2) * (ascending ? 1 : -1);
        }
    });
}

I tried Function<? extends Comparable<?>, T> and Function<? extends Comparable<? extends Comparable>, T> but neither compiled, with an error on the call to compareTo. For the former that is:

The method compareTo(capture#9-of ?) in the type Comparable is not applicable for the arguments (capture#10-of ? extends Comparable)

Bart van Heukelom
  • 43,244
  • 59
  • 186
  • 301
  • Can you also provide the `Function` class? – melihcelik Dec 07 '11 at 12:58
  • 1
    Hi, I'm taking a look at the question and not really getting anywhere. But one comment I have is, don't negate the result of `compareTo()`, because if anyone returns `Integer.MIN_VALUE` it will remain `Integer.MIN_VALUE` and the sort order won't be what you intended. Instead, when ascending is false, reverse the call, e.g. from `a.compareTo(b)` to `b.compareTo(a);`. Annoying I know... – Grundlefleck Dec 07 '11 at 13:17
  • @Grundlefleck Thanks, I implemented that. – Bart van Heukelom Dec 07 '11 at 14:24

3 Answers3

5

Try this:

public static <T, C extends Comparable<? super C>> void sortByValue(List<T> list, final boolean ascending, final Function<C, T> valueFunction) {
    Collections.sort(list, new Comparator<T>() {
        @Override public int compare(T o1, T o2) {
            final C v1 = valueFunction.apply(o1);
            final C v2 = valueFunction.apply(o2);
            return v1.compareTo(v2) * (ascending ? 1 : -1);
        }
    });
}

you also need the super to allow comparators defined for sub types. More explanations here: http://docs.oracle.com/javase/tutorial/extra/generics/morefun.html

UPDATE

Also, looking at your code I see yet another bicycle, there is a good library the Google Collections, which provides very convenient Ordering notion to handle it.

So, your code would look like:

Ordering<NormalTile> myOrdering = Ordering.natural()
  .onResultOf(new Function<Integer,NormalTile>() {
  public Integer call(NormalTile t) {
      return t.getNumber();
  }))
  .nullsLast();
...
Collections.sort(list, myOrdering);
//or
newList = myOrdering.sortedCopy(readonlyList);
kan
  • 28,279
  • 7
  • 71
  • 101
  • Hmm, you still have the `Comparable` raw type... And you probably meant `? super C`, not `T`. In that case, `super` is a good idea, but not needed as there is no other argument depending on `C` – Lukas Eder Dec 07 '11 at 13:06
  • It works, though I had to change `apply` back to `call`. And I'm already using Google Collections (in Guava) so perhaps I'll do away with this function entirely. Good for learning generics though. – Bart van Heukelom Dec 07 '11 at 14:19
2

This works for me (Eclipse compiler)

public static <T, U extends Comparable<U>> void sortByValue(
  List<T> list, final boolean ascending, final Function<U, T> valueFunction) {

  Collections.sort(list, new Comparator<T>() {
    @Override
    public int compare(T o1, T o2) {
      final U v1 = valueFunction.call(o1);
      final U v2 = valueFunction.call(o2);
      return v1.compareTo(v2) * (ascending ? 1 : -1);
    }
  });
}

As others posted, you may even go further and declare U as

U extends Comparable<? super U>

That will come in handy if you have more method arguments / return values depending on U

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
1

What if you declare two parameters for the function?

public static <T,C extends Comparable<C>> void sortByValue(List<T> list,
    final boolean ascending, final Function<C, T> valueFunction) {
...
final C v1 = ...
final C v2  ...

Haven't sanity checked myself with a compiler (don't have your interfaces and am too hungry to mock them :) ), but give it a shot.

I'm also too groggy to reason over whether it should be C extends Comparable<C> or C extends Comparable<? super C>. I think the former would work and be a tad more general, although in practice, most classes don't implement Comparable except against themselves.

yshavit
  • 42,327
  • 7
  • 87
  • 124