5

Let's say that I have an array with objects, where I have some employees (objects). They all have: int age, double salary. I want to sort this array so my class implements Comparable <Employee>. I've made a method:

public int compareTo(Employee other) {
    return Double.compare(salary, other.salary);
}

And it's ok, sorting is working fine. But I'm sorting by double salary. Now I want to sort by int age so what now? I've made a method:

public int compareAge(Employee other) {
    return Integer.compare(age, other.age);
}

And how can I use Arrays.sort() with this? I want to have possibility to use both methods - sort by salary, sort by age. Thank you for help.

nem035
  • 34,790
  • 6
  • 87
  • 99
RichardK
  • 3,228
  • 5
  • 32
  • 52

2 Answers2

9

To implement multiple ways of sorting a collection of Employee references, you should create separate classes implementing Comparator<Employee>. So you might have:

public class EmployeeAgeComparator implements Comparator<Employee> {
    ...
}

public class EmployeeSalaryComparator implements Comparator<Employee> {
    ...
}

Then you just pass an instance of the appropriate comparator into the Arrays.sort method.

Basically, implementing Comparable is good when there's one sort order which is a sensible default - but comparators allow you to separate "the things being compared" from "the thing doing the comparing."

As a side-note, using double to represent currency values (like salaries) is a bad idea, due to the way binary floating point works (e.g. not being able to represent 0.1 exactly)... use BigDecimal, or store an integer number of cents (or whatever currency unit you're using).

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thank you, but I can't access private fields for int age, double salary which are Employee class fields. – RichardK Sep 28 '14 at 10:29
  • 2
    @RichardK: And the class doesn't expose them as properties somewhere? You could create a method to create a comparator using an anonymous inner class, but using properties would be cleaner IMO. Sorting by unobservable values is fairly odd. – Jon Skeet Sep 28 '14 at 10:38
  • Wouldn't it generally be better form as of Java 8 to implement these as instances initialized by lambda expressions or `Comparator.comparing`? – Alex Godofsky Sep 29 '14 at 21:51
  • @Alex: yes, that would make a lot of sense. I don't use Java 8 day-to-day yet, so I don't think of Java 8 idioms first. Will edit when I get a chance. – Jon Skeet Sep 29 '14 at 21:52
5

You should use two Comparator classes instead of implementing Comparable.

In short, a class that implements Comparable will be comparable in a single aspect to instances of that class.

A class that implements Comparator will be a comparator medium for some other class. This means you can have multiple comparators to compare classes for different aspects. Furthermore, a Comparator class can be passed to a sort method, such as Collections.sort() or Arrays.sort(), to allow precise control over the sort order and can also be used to control the order of certain data structures, such as sorted sets or sorted maps.

To serve your purpose, what you could do is create two Comparator classes like:

class SalaryComparator implements Comparator<Employee> {
    int compare(Employee a, Employee b) {
        return Double.compare(a.salary, b.salary);
    }
}

class AgeComparator  implements Comparator<Employee> {
    int compare(Employee a, Employee b) {
        return Integer.compare(a.age, b.age);
    }
}

And then when calling a sorting method you pass in a Comparator you would like to use.

For example, if you had an ArrayList<Employee> list and you want to sort it by salary you can do something like:

 Collections.sort(list, new SalaryComparator()); // sort the list by salaries

Or if you had an Employee[] array and you want to sort it by age for example:

Arrays.sort(array, new AgeComparator()); // sort the array by age
Brian Risk
  • 1,244
  • 13
  • 23
nem035
  • 34,790
  • 6
  • 87
  • 99
  • 3
    An added bonus here is that `Collections.sort()` is guaranteed to be conservative. If you want to sort employees by salary and those with equal salaries by age, all you need to do is to sort the list by age first, then by salary. – biziclop Sep 28 '14 at 09:20
  • Thank you, but I can't access private fields for int age, double salary which are Employee class fields. – RichardK Sep 28 '14 at 10:29
  • No problem and you should just add public getter methods for salary and age like `public int getAge() { return this.age; }` inside your `Employee` class and then use it inside the comparator like `other.getAge()` instead of `other.age`. Do similarly for salary. – nem035 Sep 28 '14 at 17:13
  • 1
    Another way you could solve this is to add these two comparator classes inside your `Employee` class because inner classes have access to private member of their outer class. – nem035 Sep 28 '14 at 17:23
  • Thank you, it works like this: public class CompareAge implements Comparator { @Override public int compare(Employee a, Employee b) { return Integer.compare(a.getAge(), b.getAge()); } – RichardK Sep 28 '14 at 20:28
  • Basicly I had to compare 2 objects, I used Employee a, Employee b. – RichardK Sep 28 '14 at 20:37
  • @RichardK yes that's a way to go, I just realized I forgot to add two parameters to the `compare` function, fixed it :D – nem035 Sep 28 '14 at 22:53