Java предоставляет встроенные методы для сортировки массива примитивных типов, объектов, массивов и списков. В этой статье мы разберем различные способы сортировки используя java.lang.Comparable
и java.util.Comparator
.
Для начала давайте посмотрим пример сортировки примитивных типов и пример сортировки массива объектом в программе ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package ua.com.prologistic; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; public class JavaObjectSorting { public static void main(String[] args) { // сортируем массив int'ов int[] intArr = {5,9,1,10}; Arrays.sort(intArr); System.out.println(Arrays.toString(intArr)); // сортируем массив объектов String String[] strArr = {"A", "C", "B", "Z", "E"}; Arrays.sort(strArr); System.out.println(Arrays.toString(strArr)); // сортируем список объектов классов-оболочек List<String> strList = new ArrayList<String>(); strList.add("A"); strList.add("C"); strList.add("B"); strList.add("Z"); strList.add("E"); // сортируем список встроенным методов сортировки класса Collections Collections.sort(strList); for (String str: strList) { System.out.print(" " + str); } } } |
В результате получим следующий вывод в консоль:
1 2 3 |
[1, 5, 9, 10] [A, B, C, E, Z] A B C E Z |
Так, а теперь давайте попробуем отсортировать массив объектов нашего собственного класса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package ua.com.prologistic; public class Employee { private int id; private String name; private int age; private long salary; public int getId() { return id; } public String getName() { return name; } public int getAge() { return age; } public long getSalary() { return salary; } public Employee(int id, String name, int age, int salary) { this.id = id; this.name = name; this.age = age; this.salary = salary; } @Override // переопределим метод toString() для печати // информации об объекте Employee в читабельном виде public String toString() { return "[id=" + this.id + ", name=" + this.name + ", age=" + this.age + ", salary=" + this.salary + "]"; } } |
Ниже показан фрагмент кода для сортировки массива объектов Employee:
1 2 3 4 5 6 7 8 9 10 |
// создаем массив объектов Employee Employee[] empArr = new Employee[4]; // заполняем массив объектов Employee данными empArr[0] = new Employee(10, "Andrew", 21, 10000); empArr[1] = new Employee(20, "Dmitriy", 49, 20000); empArr[2] = new Employee(5, "Viktor", 45, 5000); empArr[3] = new Employee(1, "Alex", 42, 50000); // сортируем массив с использованием Comparable Arrays.sort(empArr); System.out.println("Стандартная сортировка для массива объектов Employee:\n"+Arrays.toString(empArr)); |
Теперь запустим программу и получим ошибку:
1 2 3 4 5 6 |
Exception in thread "main" java.lang.ClassCastException: ua.com.prologistic.Employee cannot be cast to java.lang.Comparable at java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:290) at java.util.ComparableTimSort.sort(ComparableTimSort.java:157) at java.util.ComparableTimSort.sort(ComparableTimSort.java:146) at java.util.Arrays.sort(Arrays.java:472) at ua.com.prologistic.JavaSorting.main(JavaSorting.java:41) |
В чем причина? Дело в том, что если пользовательский класс в Java хочет использовать встроенные методы сортировки классов Arrays
или Collections
, то он должен реализовать интерфейс Comparable. Этот интерфейс предоставляет метод compareTo(T obj)
, который используется методами сортировки. Вы можете убедиться в этом сами, просто посмотрев на реализацию классов String
или Date
.
Для того, чтобы избежать этой ошибки мы должны переопределить метод compareTo(T obj)
таким образом, чтобы он возвращал отрицательное число, ноль или положительное целое число, если «этот» объект меньше, равен или больше объекта, переданного в качестве аргумента.
После реализации интерфейса Comparable класс Employee изменится следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
package ua.com.prologistic; import java.util.Comparator; public class Employee implements Comparable<Employee> { private int id; private String name; private int age; private long salary; public int getId() { return id; } public String getName() { return name; } public int getAge() { return age; } public long getSalary() { return salary; } public Employee(int id, String name, int age, int salary) { this.id = id; this.name = name; this.age = age; this.salary = salary; } @Override public int compareTo(Employee emp) { //давайте будем сортировать объекты Employee по значению поля id от меньшего к большему //будем возвращать отрицательное число, 0 или положительное число по каждому сравнению объектов // здесь мы просто отнимаем значение поля одного объекта от значения поля другого объекта // в результате получим 1 из 3 вариантов описанных выше вариантов return (this.id - emp.id); } @Override // переопределяем метод таким образом, чтобы // он возвращаем информацию об объекте в читабельном виде public String toString() { return "[id=" + this.id + ", name=" + this.name + ", age=" + this.age + ", salary=" + this.salary + "]"; } } |
Теперь, когда мы запустим этот код, то получим в консоль вывод отсортированного массива в читабельном виде:
1 2 |
Стандартная сортировка для массива объектов Employee: [[id=1, name=Alex, age=32, salary=50000], [id=5, name=Viktor, age=35, salary=5000], [id=10, name=Andrew, age=25, salary=10000], [id=20, name=Dmitriy, age=29, salary=20000]] |
Как видно из вывода в консоль, массив объектов Employee отсортирован по id в порядке возрастания.
Но в большинстве реальных проектов нужно несколько вариантов сортировки с различными параметрами, а не только id. Например можно отсортировать служащих по уровню заработной платы или по возрасту.
С помощью метода Comparable.compareTo(Object o)
можно отсортировать объекты только по одному полю. Вот для таких вот ситуаций лучше всего использовать компаратор.
Метод компаратора compare(Object o1, Object o2)
принимает два объекта в качестве аргумента и должен быть реализован таким образом, чтобы возвращать отрицательное число — если первый аргумент меньше второго, ноль — если они равны и положительное число, если первый аргумент больше, чем второй.
Оба интерфейса Comparable
и Comparator
использует дженерики.
Давайте посмотрим на реализацию компаратора в классе Employee:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// компаратор сортирует список или массив объектов по зарплате public static Comparator<Employee> SalaryComparator = new Comparator<Employee>() { @Override public int compare(Employee e1, Employee e2) { return (int) (e1.getSalary() - e2.getSalary()); } }; // компаратор сортирует список или массив объектов по возрасту public static Comparator<Employee> AgeComparator = new Comparator<Employee>() { @Override public int compare(Employee e1, Employee e2) { return e1.getAge() - e2.getAge(); } }; // компаратор сортирует список или массив объектов по имени public static Comparator<Employee> NameComparator = new Comparator<Employee>() { @Override public int compare(Employee e1, Employee e2) { return e1.getName().compareTo(e2.getName()); } }; |
Все представленные выше реализации интерфейса Comparator являются анонимными классами.
Мы можем использовать эти компараторы передавая их в качестве аргумента для сортировки массивов или коллекций.
1 2 3 4 5 6 7 8 9 10 11 |
//сортируем массив объектов с помощью Comparator по зарплате Arrays.sort(empArr, Employee.SalaryComparator); System.out.println("Сортировка по зарплате:\n"+Arrays.toString(empArr)); //сортируем массив объектов с помощью Comparator по возрасту Arrays.sort(empArr, Employee.AgeComparator); System.out.println("Сортировка по возрасту:\n"+Arrays.toString(empArr)); //сортируем массив объектов с помощью Comparator по имени Arrays.sort(empArr, Employee.NameComparator); System.out.println("Сортировка по имени:\n"+Arrays.toString(empArr)); |
Итак, теперь мы можем сортировать любой массив или список объектов. Для этого мы должны реализовать Comparator под каждое конкретное поле.
Мы также можем создать отдельный класс, который реализует интерфейс компаратора и затем использовать его.
Ниже приведена окончательная версия с реализованным интерфейсом Comparable и Comparator:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
package ua.com.prologistic; import java.util.Comparator; public class Employee implements Comparable<Employee> { private int id; private String name; private int age; private long salary; public int getId() { return id; } public String getName() { return name; } public int getAge() { return age; } public long getSalary() { return salary; } public Employee(int id, String name, int age, int salary) { this.id = id; this.name = name; this.age = age; this.salary = salary; } @Override public int compareTo(Employee emp) { //давайте будем сортировать объекты Employee по значению поля id от меньшего к большему //будем возвращать отрицательное число, 0 или положительное число по каждому сравнению объектов // здесь мы просто отнимаем значение поля одного объекта от значения поля другого объекта // в результате получим 1 из 3 вариантов описанных выше вариантов return (this.id - emp.id); } @Override // переопределяем метод таким образом, чтобы // он возвращаем информацию об объекте в читабельном виде public String toString() { return "[id=" + this.id + ", name=" + this.name + ", age=" + this.age + ", salary=" + this.salary + "]"; } //Comparator для сортировки списка или массива объектов по зарплате public static Comparator<Employee> SalaryComparator = new Comparator<Employee>() { @Override public int compare(Employee e1, Employee e2) { return (int) (e1.getSalary() - e2.getSalary()); } }; //Comparator для сортировки списка или массива объектов по возрасту public static Comparator<Employee> AgeComparator = new Comparator<Employee>() { @Override public int compare(Employee e1, Employee e2) { return e1.getAge() - e2.getAge(); } }; //Comparator для сортировки списка или массива объектов по имени public static Comparator<Employee> NameComparator = new Comparator<Employee>() { @Override public int compare(Employee e1, Employee e2) { return e1.getName().compareTo(e2.getName()); } }; } |
А теперь усложним наш пример и вынесем нашу сортировку в отдельный класс и добавим еще одно условие сортировки:
Ниже представлен отдельный класс, который реализует интерфейс Comparator и будет сравнивать два объекта по id, и если они одинаковы, то по имени:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package ua.com.prologistc; import java.util.Comparator; public class CompByIdAndName implements Comparator<Employee> { @Override public int compare(Employee o1, Employee o2) { // отнимает id и получаем результат в переменную flag int flag = o1.getId() - o2.getId(); // если получили 0, то сортируем по имени if(flag == 0) flag = o1.getName().compareTo(o2.getName()); return flag; } } |
Вот тестовый класс, где мы используем различные способы сортировки объектов в Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package ua.com.prologistic; import java.util.Arrays; public class JavaObjectSorting { // в этом классе сортируем массивы и списки // объектов с помощь. интерфейсов Comparable и Comparator public static void main(String[] args) { // создаем массив объектов Employee[] empArr = new Employee[4]; empArr[0] = new Employee(10, "Andrew", 21, 10000); empArr[1] = new Employee(20, "Viktor", 49, 20000); empArr[2] = new Employee(5, "Lena", 45, 5000); empArr[3] = new Employee(1, "Alex", 42, 50000); // сортируем массив с помощью интерфейса Comparable Arrays.sort(empArr); System.out.println("Стандартная сортировка:\n"+Arrays.toString(empArr)); // сортируем массив с помощью интерфейса Comparator по зарплате Arrays.sort(empArr, Employee.SalaryComparator); System.out.println("Comparator по зарплате:\n"+Arrays.toString(empArr)); //сортируем массив с помощью интерфейса Comparator по возрасту Arrays.sort(empArr, Employee.AgeComparator); System.out.println("Comparator по возрасту:\n"+Arrays.toString(empArr)); // сортируем массив с помощью интерфейса Comparator по имени Arrays.sort(empArr, Employee.NameComparator); System.out.println(" Comparator по имени:\n"+Arrays.toString(empArr)); // сортируем массив по id и по имени с помощью Comparator empArr[0] = new Employee(1, "Mikhail", 23, 10000); Arrays.sort(empArr, new EmployeeComparatorByIdAndName()); System.out.println("Сортируем по id и по имени:\n"+Arrays.toString(empArr)); } } |
Сегодня мы узнали, что java.lang.Comparable
и java.util.Comparator
— это мощные интерфейсы для сортировки объектов в Java.
Ну как всегда на самом интересном месте и не работает(
Не работающая строчка: Arrays.sort(empArr, new EmployeeComparatorByIdAndName()); (в самом низу последнего листинга)
Сперва подумал что опечатка, поправил не помогло… Как это класс подключить то?
Разобрался!!! Надо заменить EmployeeComparatorByIdAndName на CompByIdAndName и дополнить массив того что будем сортировать!
Новый класс CompByIdAndName должен быть static,иначе выскочит ошибка.