88

I want to sort my objects in descending order using comparator.

class Person {
 private int age;
}

Here I want to sort a array of Person objects.

How can I do this?

Jav_Rock
  • 22,059
  • 20
  • 123
  • 164
Manoj
  • 5,707
  • 19
  • 56
  • 86

7 Answers7

136

You can do the descending sort of a user-defined class this way overriding the compare() method,

Collections.sort(unsortedList,new Comparator<Person>() {
    @Override
    public int compare(Person a, Person b) {
        return b.getName().compareTo(a.getName());
    }
});

Or by using Collection.reverse() to sort descending as user Prince mentioned in his comment.

And you can do the ascending sort like this,

Collections.sort(unsortedList,new Comparator<Person>() {
    @Override
    public int compare(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
});

Replace the above code with a Lambda expression(Java 8 onwards) we get concise:

Collections.sort(personList, (Person a, Person b) -> b.getName().compareTo(a.getName()));

As of Java 8, List has sort() method which takes Comparator as parameter(more concise) :

personList.sort((a,b)->b.getName().compareTo(a.getName()));

Here a and b are inferred as Person type by lambda expression.

Community
  • 1
  • 1
Lucky
  • 16,787
  • 19
  • 117
  • 151
  • 27
    Or you can just use `Collections.reverseOrder(...)` for descending sort. – Prince Feb 17 '14 at 21:40
  • 1
    @Prince Not a fan of that approach, it forces me to look at the original method to see how things are sorted to begin with (un-reversed.) – arkon Aug 06 '15 at 03:21
  • 1
    Worth mentioning that `compareToIgnoreCase` is also handy when comparing `String` objects, more often than not I use this instead of just `compareTo` – Mark Jun 06 '17 at 22:43
  • Worked like a charm! Thank you. – Mr. Port St Joe Nov 04 '17 at 04:52
70

For whats its worth here is my standard answer. The only thing new here is that is uses the Collections.reverseOrder(). Plus it puts all suggestions into one example:

/*
**  Use the Collections API to sort a List for you.
**
**  When your class has a "natural" sort order you can implement
**  the Comparable interface.
**
**  You can use an alternate sort order when you implement
**  a Comparator for your class.
*/
import java.util.*;

public class Person implements Comparable<Person>
{
    String name;
    int age;

    public Person(String name, int age)
    {
        this.name = name;
        this.age = age;
    }

    public String getName()
    {
        return name;
    }

    public int getAge()
    {
        return age;
    }

    public String toString()
    {
        return name + " : " + age;
    }

    /*
    **  Implement the natural order for this class
    */
    public int compareTo(Person p)
    {
        return getName().compareTo(p.getName());
    }

    static class AgeComparator implements Comparator<Person>
    {
        public int compare(Person p1, Person p2)
        {
            int age1 = p1.getAge();
            int age2 = p2.getAge();

            if (age1 == age2)
                return 0;
            else if (age1 > age2)
                return 1;
            else
                return -1;
        }
    }

    public static void main(String[] args)
    {
        List<Person> people = new ArrayList<Person>();
        people.add( new Person("Homer", 38) );
        people.add( new Person("Marge", 35) );
        people.add( new Person("Bart", 15) );
        people.add( new Person("Lisa", 13) );

        // Sort by natural order

        Collections.sort(people);
        System.out.println("Sort by Natural order");
        System.out.println("\t" + people);

        // Sort by reverse natural order

        Collections.sort(people, Collections.reverseOrder());
        System.out.println("Sort by reverse natural order");
        System.out.println("\t" + people);

        //  Use a Comparator to sort by age

        Collections.sort(people, new Person.AgeComparator());
        System.out.println("Sort using Age Comparator");
        System.out.println("\t" + people);

        //  Use a Comparator to sort by descending age

        Collections.sort(people,
            Collections.reverseOrder(new Person.AgeComparator()));
        System.out.println("Sort using Reverse Age Comparator");
        System.out.println("\t" + people);
    }
}
camickr
  • 321,443
  • 19
  • 166
  • 288
  • this actually worked for me , and it's also a very easy solution (just one line) . – android developer Dec 24 '12 at 09:59
  • Is there a way to sort Strings in a TreeSet using a comparator like this? Same thing, People by age. – Zeff520 May 13 '13 at 14:00
  • I've never used a TreeSet, but the API documentation says - *The elements are ordered using their natural ordering, or by a Comparator provided at set creation time, depending on which constructor is used,* so I would guess it is possible. – camickr May 13 '13 at 15:13
21

I would create a comparator for the person class that can be parametrized with a certain sorting behaviour. Here I can set the sorting order but it can be modified to allow sorting for other person attributes as well.

public class PersonComparator implements Comparator<Person> {

  public enum SortOrder {ASCENDING, DESCENDING}

  private SortOrder sortOrder;

  public PersonComparator(SortOrder sortOrder) {
    this.sortOrder = sortOrder;
  }

  @Override
  public int compare(Person person1, Person person2) {
    Integer age1 = person1.getAge();
    Integer age2 = person2.getAge();
    int compare = Math.signum(age1.compareTo(age2));

    if (sortOrder == ASCENDING) {
      return compare;
    } else {
      return compare * (-1);
    }
  }
}

(hope it compiles now, I have no IDE or JDK at hand, coded 'blind')

Edit

Thanks to Thomas, edited the code. I wouldn't say that the usage of Math.signum is good, performant, effective, but I'd like to keep it as a reminder, that the compareTo method can return any integer and multiplying by (-1) will fail if the implementation returns Integer.MIN_INTEGER... And I removed the setter because it's cheap enough to construct a new PersonComparator just when it's needed.

But I keep the boxing because it shows that I rely on an existing Comparable implementation. Could have done something like Comparable<Integer> age1 = new Integer(person1.getAge()); but that looked too ugly. The idea was to show a pattern which could easily be adapted to other Person attributes, like name, birthday as Date and so on.

Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
  • 6
    Once upon a time there was a good habit to leave a comment to help the just downvoted author to improve his message. – Andreas Dolk Dec 22 '09 at 16:12
  • 1
    I did not downvote, but `compare * (-1)` is prone to overflow. I did the same mistake in my initial post. – Thomas Jung Dec 22 '09 at 16:14
  • And the SortOrder should be set in the constructor and be final. Using a wrapper is an better approach I suppose: `new Reverse(new PersonComparator())` – Thomas Jung Dec 22 '09 at 16:17
  • And `Integer age1 = ...;` has the boxing overhead. – Thomas Jung Dec 22 '09 at 16:18
  • Yes, yes, yes, enough room for improvement. Boxing was on purpose and the setter allows to use the comparator for ascending and descending ordering - on the other hand, you're right, better create a new one each time then reusing the same. Constructing is cheap enough. – Andreas Dolk Dec 22 '09 at 21:31
  • 1
    Why did you not swap the parameters for descending sort order? That much easier: `Integer age1 = sortOrder == ASCENDING ? person1.getAge() : person2.getAge(); Integer age2 = sortOrder == ASCENDING ? person2.getAge() : person1.getAge(); return age1.compareTo(age2);` – Thomas Jung Dec 23 '09 at 12:26
  • `new Integer(person1.getAge())` will be a worse solution. This will always create a new instance. `int a = ...; Integer x = a;` will be compiled to `int a = ...; Integer x = Integer.valueOf(a);` which will be cached for normal ages (i <= 127). So my comment was not 100% correct for this use case. – Thomas Jung Dec 23 '09 at 12:31
  • 1
    What’s the rationale behind using `compare * (-1)` instead of a straight-forward `-compare`? Using negation when you want a negation is idiomatic and flipping the sign is cheaper than performing a multiplication. – Holger Jun 21 '22 at 18:15
  • IMHO, arguing with performance is usually wrong, unless it's in terms of O-notation. I think, I wanted to create a version that I considered to be very readable at that time. I probably thought, that was more understandable then doing `-compare`. Matter of taste, at this point? But before optimizing performance on THAT line: there are better candidates in the solution if you want to sort zillions of persons in microseconds. Maybe, switching to machine code :) (tl;dr: Clean code matters) – Andreas Dolk Aug 10 '22 at 09:50
16
String[] s = {"a", "x", "y"};
Arrays.sort(s, new Comparator<String>() {

    @Override
    public int compare(String o1, String o2) {
        return o2.compareTo(o1);
    }
});
System.out.println(Arrays.toString(s));

-> [y, x, a]

Now you have to implement the Comparator for your Person class. Something like (for ascending order): compare(Person a, Person b) = a.id < b.id ? -1 : (a.id == b.id) ? 0 : 1 or Integer.valueOf(a.id).compareTo(Integer.valueOf(b.id)).

To minimize confusion you should implement an ascending Comparator and convert it to a descending one with a wrapper (like this) new ReverseComparator<Person>(new PersonComparator()).

Thomas Jung
  • 32,428
  • 9
  • 84
  • 114
  • Now that's a bit confusing example, because String implements Comparable. And that -1 there isn't the most straighforward thing. – Bozho Dec 22 '09 at 14:42
  • 1
    The best argument against -1 * x is that `-1 * Integer.MIN_VALUE == Integer.MIN_VALUE`. Which is not what you want. I swapped the arguments that's easier anyway. – Thomas Jung Dec 22 '09 at 14:56
  • 1
    And I guess it's a ReverseComparator, instead of Reserve... – Adriaan Koster Dec 22 '09 at 15:02
  • well, that wasn't THE best argument. It was just confusing for beginners :) It still is, because it would require him to know what Comparable is, and realize that his Person doesn't implement it. But then - thinking IS a good thing :) – Bozho Dec 22 '09 at 15:06
  • `Integer.compare` mentions the following in its documentation: `The value returned is identical to what would be returned by: Integer.valueOf(x).compareTo(Integer.valueOf(y))`, so you can use `Integer.compare` instead. – keyser Jul 11 '16 at 12:33
5

Using Google Collections:

class Person {
 private int age;

 public static Function<Person, Integer> GET_AGE =
  new Function<Person, Integer> {
   public Integer apply(Person p) { return p.age; }
  };

}

public static void main(String[] args) {
 ArrayList<Person> people;
 // Populate the list...

 Collections.sort(people, Ordering.natural().onResultOf(Person.GET_AGE).reverse());
}
finnw
  • 47,861
  • 24
  • 143
  • 221
0

The java.util.Collections class has a sort method that takes a list and a custom Comparator. You can define your own Comparator to sort your Person object however you like.

Lucky
  • 16,787
  • 19
  • 117
  • 151
Robert Christie
  • 20,177
  • 8
  • 42
  • 37
0
package com.test;

import java.util.Arrays;

public class Person implements Comparable {

private int age;

private Person(int age) {
    super();
    this.age = age;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

@Override
public int compareTo(Object o) {
    Person other = (Person)o;
    if (this == other)
        return 0;
    if (this.age < other.age) return 1;
    else if (this.age == other.age) return 0;
    else return -1;

}

public static void main(String[] args) {

    Person[] arr = new Person[4];
    arr[0] = new Person(50);
    arr[1] = new Person(20);
    arr[2] = new Person(10);
    arr[3] = new Person(90);

    Arrays.sort(arr);

    for (int i=0; i < arr.length; i++ ) {
        System.out.println(arr[i].age);
    }
}

}

Here is one way of doing it.

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
Shamik
  • 6,938
  • 11
  • 55
  • 72