Научитесь сортировать список или поток Java с помощью методов nullsFirst() и nullsLast() Comparator. Поток может содержать значения null, или пользовательские объекты могут иметь значения полей null.
Неспособность обработать значения NULL во время сравнения приведет к возникновению исключения NullPointerException во время выполнения.
1. Введение
В этом уроке мы изучим использование следующих методов:
- Comparator.nullsFirst() – Возвращает компаратор, дружественный к null, если хотя бы одно значение(при сравнении) равно null. Он считает null меньшим, чем не-null. Если обе стороны сравнения равны null, то обе считаются равными.
- Comparator.nullsLast() – – Возвращает компаратор, дружественный к null, если хотя бы одно значение(при сравнении) равно null. Он считает null большим, чем не null. Если обе стороны сравнения равны null, то обе считаются равными.
Фактически, оба метода выше возвращают экземпляр NullComparator, который добавляет необходимые проверки на null при сравнении обеих сторон для целей сортировки.
Здесь nullFirst относится к подходу null-first или null-last. А real относится к исходному Comparator, если обе стороны не являются нулевыми значениями.
статический финальный класс NullComparator<T>реализует Comparator<T>, Сериализуемый {......public int сравнить(T a, T b) {если(а == ноль) {возврат(b == null) ? 0 :(nullFirst ? -1 : 1);} иначе если(b == null) {вернуть nullFirst ? 1: -1;} еще {возврат(действительный == null) ? 0 : действительный.сравнить(a, b);}}......}
2. Сортировка потока пользовательских объектов
Предположим, что мы сортируем список объектов Employee. Мы хотим отсортировать заданный поток сотрудников по дате их рождения.
Сотрудник общественного класса{частный длинный идентификатор;личное имя строки;частная ЛокальнаяДата датаРождения;частная двойная зарплата;//конструкторы, сеттеры и геттеры скрыты для краткости@Переопределитьпубличная строка toString() {вернуть "Сотрудник [id=" + id + ", name=" + name + ", dateOfBirth="+ датаРождения + ", зарплата=" + зарплата + "]";}}
2.1 Несколько пользовательских объектов в списке имеют значение Null
Теперь предположим, что есть несколько объектов сотрудников, которые являются нулевыми. В данном примере мы добавили два нуля в поток вместе с 3 сотрудниками.
Мы сортируем с помощью метода nullsFirst(), после сортировки в начале будут нулевые значения, а затем список сортировки сотрудников будет отсортирован по дате рождения.
Для сортировки в естественном порядке после упорядочивания нулевых значений используйте Comparator.nullsFirst( Comparator.naturalOrder() ).
открытый класс DateOfBirthComparator реализует Comparator<Employee> {@Переопределитьpublic int compare(final Employee e1, final Employee e2) {вернуть e1.getDateOfBirth().compareTo(e2.getDateOfBirth());}}
Список sortedEmployeeList = getEmployeeListWithNullObjects().транслировать().sorted(Comparator.nullsFirst(new DateOfBirthComparator())).собрать(Collectors.toList());System.out.println(sortedEmployeeList);......частный статический список<Сотрудник> getEmployeeListWithNullObjects() {Список empList = new ArrayList<>();empList.add(new Employee(1, "A", LocalDate.of(1992, 1, 1), 30000d));empList.add(null);empList.add(new Employee(3, "C", LocalDate.of(1992, 9, 1), 50000d));empList.add(null);empList.add(new Employee(5, "E", LocalDate.of(1992, 8, 1), 60000d));вернуть empList;}
Вывод программы.
[нулевой,нулевой,Сотрудник [id=1, name=A, dateOfBirth=1992-01-01, salary=30000.0],Сотрудник [id=5, name=E, dateOfBirth=1992-08-01, salary=60000.0],Сотрудник [id=3, name=C, dateOfBirth=1992-09-01, salary=50000.0]]
2.2 Значения полей пользовательского объекта равны нулю
В некоторых сценариях у нас может быть поток объектов, где объекты не являются нулевыми, но их значения полей могут быть нулевыми. Например, в потоке сотрудников все объекты сотрудников могут быть ненулевыми, но у нескольких сотрудников может не быть информации о дате рождения.
В таких случаях у нас есть два варианта сортировки экземпляров сотрудников:
2.2.1. Реализация дополнительных проверок на нуль в пользовательском компараторе
Чтобы избежать исключения NullPointerException, мы можем самостоятельно написать проверки на значение null в пользовательском классе Comparator.
открытый класс DateOfBirhComparator реализует Comparator<Employee> {@Переопределитьpublic int compare(final Employee e1, final Employee e2) {если(e1.getDateOfBirth() == null && e2.getDateOfBirth() == null) {возврат 0;} иначе если(e1.getDateOfBirth() == null) {возврат -1;} иначе если(e2.getDateOfBirth() == null) {возврат 1;} еще {вернуть e1.getDateOfBirth().compareTo(e2.getDateOfBirth());}}}
И затем мы используем этот компаратор для сортировки. Обратите внимание на вывод, что первые две записи имеют поле даты рождения как null.
sortedEmployeeList = getEmployeeListWithNullDates().stream().sorted(новый DateOfBirhComparator()).собрать(Collectors.toList());частный статический список<Сотрудник> getEmployeeListWithNullDates() {Список empList = new ArrayList<>();empList.add(new Employee(1, "A", LocalDate.of(1991, 1, 1), 30000d));empList.add(новый сотрудник(2, "B", null, 20000d));empList.add(new Employee(3, "C", LocalDate.of(1992, 8, 1), 50000d));empList.add(new Employee(4, "D", LocalDate.of(2001, 3, 11), 50000d));empList.add(новый сотрудник(5, "E", null, 60000d));вернуть empList;}
[Сотрудник [id=2, name=B, dateOfBirth=null, salary=20000.0],Сотрудник [id=5, name=E, dateOfBirth=null, salary=60000.0],Сотрудник [id=1, name=A, dateOfBirth=1991-01-01, salary=30000.0],Сотрудник [id=3, name=C, dateOfBirth=1992-08-01, salary=50000.0],Сотрудник [id=4, name=D, dateOfBirth=2001-03-11, salary=50000.0]]
Обратите внимание, что если мы хотим обработать оба сценария, где либо объект Employee может быть пустым, либо поле dateOfBirth может быть пустым, то мы можем использовать комбинацию DateOfBirhComparator и NullComparator.
sortedEmployeeList = getEmployeeListWithNullDates().stream().sorted(Comparator.nullsFirst(new DateOfBirhComparator())).collect(Collectors.toList());
2.2.2 Использование Comparator.comparing()
Мы можем предоставить встроенный пользовательский компаратор с синтаксисом Lambda следующим образом.
sortedEmployeeList = getEmployeeListWithNullDates().stream().sorted(Comparator.comparing(Employee::getDateOfBirth,Comparator.nullsFirst(Comparator.naturalOrder()))).collect(Collectors.toList());
Приведенный выше код также будет обеспечивать ту же функциональность, что и пользовательский компаратор.
3. Сортировка с нулями в естественном порядке
Для встроенных типов Java, таких как примитивы, классы-обертки и строки, мы можем использовать их естественный порядок для сортировки. Единственное, о чем нам нужно позаботиться, это то, чтобы любое значение null не нарушало код с помощью NullPointerException.
3.1. Упорядочивание нулей в конце
Используйте Comparator.nullsLast(Comparator.naturalOrder()) для упорядочивания ненулевых значений в начале и нулевых значений в конце.
List<String> names = Arrays.asList("C", null, "B", "D", null, "A", "E");List<String> sortedList = names.stream().sorted(Comparator.nullsLast(Comparator.naturalOrder())).collect(Collectors.toList());System.out.println(sortedList); //[A, B, C, D, E, null, null]
3.2. Упорядочивание нулей в начале
Используйте Comparator.nullsFirst(Comparator.naturalOrder()) для упорядочивания нулевых значений в начале и ненулевых значений в конце.
List<String> names = Arrays.asList("C", null, "B", "D", null, "A", "E");List<String> sortedList = names.stream().sorted(Comparator.nullsFirst(Comparator.naturalOrder())).collect(Collectors.toList());System.out.println(sortedList); //[null, null, A, B, C, D, E]
4. Заключение
В этом уроке Java Stream мы научились сортировать поток строк и поток пользовательских объектов. Логика сортировки сможет обрабатывать случаи, когда поток может иметь нулевые значения или объекты в потоке могут иметь нулевые поля.
Мы узнали, как NullComparator обеспечивает внутренние проверки на нуль, которые мы также можем добавить с помощью пользовательского класса Comparator.