«Это 9-й день моего участия в первом испытании обновлений 2022 года. Подробную информацию о мероприятии см.:Вызов первого обновления 2022 г."
Привет, я смотрю на гору.
Эта статья была«Продвинутая Java»В колонке вы можете получить исходный код, ответив на «java» на официальном аккаунте «The Lodge in the Mountain».
В процессе разработки системы сортировка данных является очень распространенным сценарием. Вообще говоря, мы можем сделать это двумя способами:
- Благодаря функции сортировки системы хранения (поддерживаемой SQL, NoSQL и NewSQL) результатом запроса является отсортированный результат.
- Результаты запроса представляют собой неупорядоченные данные, отсортированные в памяти.
Сегодня я хочу рассказать о втором методе сортировки, реализующем сортировку данных в памяти.
Сначала мы определяем базовый класс, а позже покажем, как выполнять сортировку в памяти на основе этого базового класса.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
на основеComparator
Сортировать
До Java8 мы все реализовывалиComparator
Интерфейс завершает сортировку, например:
new Comparator<Student>() {
@Override
public int compare(Student h1, Student h2) {
return h1.getName().compareTo(h2.getName());
}
};
Здесь показано определение анонимного внутреннего класса.Если это общая логика сравнения, класс реализации может быть определен напрямую. Он также относительно прост в использовании.Следующее приложение:
@Test
void baseSortedOrigin() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12)
);
Collections.sort(students, new Comparator<Student>() {
@Override
public int compare(Student h1, Student h2) {
return h1.getName().compareTo(h2.getName());
}
});
Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
Юнит-тестирование реализовано здесь с помощью Junit5, что очень удобно для проверки логики.
потому что определеноComparator
это использоватьname
Порядок полей в Java,String
О сортировке типов судят по кодовому порядку ASCII отдельных символов,J
РейтингT
перед, такJerry
занимает первое место.
Замена лямбда-выражениямиComparator
анонимный внутренний класс
Те из Lamdba, кто использовал Java8, должны знать, что анонимные внутренние классы могут быть упрощены до лямбда-выражений следующим образом:
Collections.sort(students, (Student h1, Student h2) -> h1.getName().compareTo(h2.getName()));
В Java8,List
добавлен классsort
метод, поэтомуCollections.sort
Можно напрямую заменить на:
students.sort((Student h1, Student h2) -> h1.getName().compareTo(h2.getName()));
В соответствии с выводом типа Lambda в Java8 мы можем назначить указанныйStudent
Введите сокращение:
students.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
На данный момент мы сортируем всю логику можно упростить как:
@Test
void baseSortedLambdaWithInferring() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12)
);
students.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
Извлечь общественные лямбда-выражения через статические методы
мы можемStudent
Определите статический метод в:
public static int compareByNameThenAge(Student s1, Student s2) {
if (s1.name.equals(s2.name)) {
return Integer.compare(s1.age, s2.age);
} else {
return s1.name.compareTo(s2.name);
}
}
Этот метод должен возвращатьint
Параметр типа, в Java8 мы можем использовать метод в Lambda:
@Test
void sortedUsingStaticMethod() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12)
);
students.sort(Student::compareByNameThenAge);
Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
с помощьюComparator
изcomparing
метод
В Java8,Comparator
добавлен классcomparing
метод, вы можете пройтиFunction
Параметры как элементы сравнения, например:
@Test
void sortedUsingComparator() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12)
);
students.sort(Comparator.comparing(Student::getName));
Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
Сортировать по нескольким условиям
Мы продемонстрировали мультиусловную сортировку в разделе, посвященном статическим методам, и ее также можно найти вComparator
Реализуйте многоусловную логику в анонимных внутренних классах:
@Test
void sortedMultiCondition() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12),
new Student("Jerry", 13)
);
students.sort((s1, s2) -> {
if (s1.getName().equals(s2.getName())) {
return Integer.compare(s1.getAge(), s2.getAge());
} else {
return s1.getName().compareTo(s2.getName());
}
});
Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
С логической точки зрения сортировка по множеству условий заключается в том, чтобы сначала оценить условия первого уровня, если они равны, затем оценить условия второго уровня и так далее. Доступно в Java8comparing
и рядthenComparing
Указывает на многоуровневое условное суждение, приведенную выше логику можно упростить следующим образом:
@Test
void sortedMultiConditionUsingComparator() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12),
new Student("Jerry", 13)
);
students.sort(Comparator.comparing(Student::getName).thenComparing(Student::getAge));
Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
здесьthenComparing
Может быть несколько методов, которые используются для представления многоуровневых условных суждений, что также является удобством функционального программирования.
существуетStream
сортировать в
В Java8 представлены не только лямбда-выражения, но и новый потоковый API: Stream API, который также имеетsorted
Метод используется для сортировки элементов в потоковых вычислениях, которые могут быть переданы вComparator
Реализуйте логику сортировки:
@Test
void streamSorted() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12)
);
final Comparator<Student> comparator = (h1, h2) -> h1.getName().compareTo(h2.getName());
final List<Student> sortedStudents = students.stream()
.sorted(comparator)
.collect(Collectors.toList());
Assertions.assertEquals(sortedStudents.get(0), new Student("Jerry", 12));
}
Точно так же мы можем упростить запись с помощью Lambda:
@Test
void streamSortedUsingComparator() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12)
);
final Comparator<Student> comparator = Comparator.comparing(Student::getName);
final List<Student> sortedStudents = students.stream()
.sorted(comparator)
.collect(Collectors.toList());
Assertions.assertEquals(sortedStudents.get(0), new Student("Jerry", 12));
}
обратный порядок
Решение обратной сортировки
Сортировать поcompareTo
Значение, возвращаемое методом, оценивается по порядку. Если вы хотите отсортировать в обратном порядке, просто верните возвращаемое значение:
@Test
void sortedReverseUsingComparator2() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12)
);
final Comparator<Student> comparator = (h1, h2) -> h2.getName().compareTo(h1.getName());
students.sort(comparator);
Assertions.assertEquals(students.get(0), new Student("Tom", 10));
}
Видно, что при расположении в положительном порядке мыh1.getName().compareTo(h2.getName())
, здесь мы обращаем его напрямую, используяh2.getName().compareTo(h1.getName())
, что имеет обратный эффект. на ЯвеCollections
определяетjava.util.Collections.ReverseComparator
Таким образом, внутренний закрытый класс реализует инверсию элементов.
с помощьюComparator
изreversed
способ в обратном порядке
Добавлено в Java8reversed
Метод реализует обратный порядок, который также очень прост в использовании:
@Test
void sortedReverseUsingComparator() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12)
);
final Comparator<Student> comparator = (h1, h2) -> h1.getName().compareTo(h2.getName());
students.sort(comparator.reversed());
Assertions.assertEquals(students.get(0), new Student("Tom", 10));
}
существуетComparator.comparing
Инверсия сортировки определена в
comparing
Метод также имеет перегруженный метод,java.util.Comparator#comparing(java.util.function.Function<? super T,? extends U>, java.util.Comparator<? super U>)
, второй параметр можно передать вComparator.reverseOrder()
, можно добиться обратного порядка:
@Test
void sortedUsingComparatorReverse() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12)
);
students.sort(Comparator.comparing(Student::getName, Comparator.reverseOrder()));
Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
существуетStream
Инверсия сортировки определена в
существуетStream
Операции аналогичны прямой сортировке списка и могут быть обратными.Comparator
определение, вы также можете использоватьComparator.reverseOrder()
Инвертировать. Реализация выглядит следующим образом:
@Test
void streamReverseSorted() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12)
);
final Comparator<Student> comparator = (h1, h2) -> h2.getName().compareTo(h1.getName());
final List<Student> sortedStudents = students.stream()
.sorted(comparator)
.collect(Collectors.toList());
Assertions.assertEquals(sortedStudents.get(0), new Student("Tom", 10));
}
@Test
void streamReverseSortedUsingComparator() {
final List<Student> students = Lists.newArrayList(
new Student("Tom", 10),
new Student("Jerry", 12)
);
final List<Student> sortedStudents = students.stream()
.sorted(Comparator.comparing(Student::getName, Comparator.reverseOrder()))
.collect(Collectors.toList());
Assertions.assertEquals(sortedStudents.get(0), new Student("Tom", 10));
}
Решение о нулевом значении
В предыдущих примерах значения элементов сортируются, что может охватывать большинство сценариев, но иногда мы все же сталкиваемся с элементами, которые существуют вnull
Случай:
- Элемент в списке является нулевым
- Поле элемента списка, участвующего в условии сортировки, равно null
Если мы по-прежнему будем использовать предыдущие реализации, мы столкнемсяNullPointException
Исключения, а именно NPE, кратко демонстрируют:
@Test
void sortedNullGotNPE() {
final List<Student> students = Lists.newArrayList(
null,
new Student("Snoopy", 12),
null
);
Assertions.assertThrows(NullPointerException.class,
() -> students.sort(Comparator.comparing(Student::getName)));
}
Итак, мы должны рассмотреть эти сценарии.
неуклюжая реализация элемента, являющегося нулевым
Первое, что приходит на ум, это быть пустым:
@Test
void sortedNullNoNPE() {
final List<Student> students = Lists.newArrayList(
null,
new Student("Snoopy", 12),
null
);
students.sort((s1, s2) -> {
if (s1 == null) {
return s2 == null ? 0 : 1;
} else if (s2 == null) {
return -1;
}
return s1.getName().compareTo(s2.getName());
});
Assertions.assertNotNull(students.get(0));
Assertions.assertNull(students.get(1));
Assertions.assertNull(students.get(2));
}
Мы можем извлечь пустую логикуComparator
, комбинируя:
class NullComparator<T> implements Comparator<T> {
private final Comparator<T> real;
NullComparator(Comparator<? super T> real) {
this.real = (Comparator<T>) real;
}
@Override
public int compare(T a, T b) {
if (a == null) {
return (b == null) ? 0 : 1;
} else if (b == null) {
return -1;
} else {
return (real == null) ? 0 : real.compare(a, b);
}
}
}
Эта реализация была подготовлена для нас в Java8.
использоватьComparator.nullsLast
а такжеComparator.nullsFirst
использоватьComparator.nullsLast
выполнитьnull
в конце:
@Test
void sortedNullLast() {
final List<Student> students = Lists.newArrayList(
null,
new Student("Snoopy", 12),
null
);
students.sort(Comparator.nullsLast(Comparator.comparing(Student::getName)));
Assertions.assertNotNull(students.get(0));
Assertions.assertNull(students.get(1));
Assertions.assertNull(students.get(2));
}
использоватьComparator.nullsFirst
выполнитьnull
В начале:
@Test
void sortedNullFirst() {
final List<Student> students = Lists.newArrayList(
null,
new Student("Snoopy", 12),
null
);
students.sort(Comparator.nullsFirst(Comparator.comparing(Student::getName)));
Assertions.assertNull(students.get(0));
Assertions.assertNull(students.get(1));
Assertions.assertNotNull(students.get(2));
}
Это очень просто?Далее давайте посмотрим, как реализовать логику, что поле условия сортировки является нулевым.
Поле условия сортировки пустое
Это с помощьюComparator
Комбинация похожа на матрешку, и ее нужно использовать дважды.Comparator.nullsLast
, реализация указана здесь:
@Test
void sortedNullFieldLast() {
final List<Student> students = Lists.newArrayList(
new Student(null, 10),
new Student("Snoopy", 12),
null
);
final Comparator<Student> nullsLast = Comparator.nullsLast(
Comparator.nullsLast( // 1
Comparator.comparing(
Student::getName,
Comparator.nullsLast( // 2
Comparator.naturalOrder() // 3
)
)
)
);
students.sort(nullsLast);
Assertions.assertEquals(students.get(0), new Student("Snoopy", 12));
Assertions.assertEquals(students.get(1), new Student(null, 10));
Assertions.assertNull(students.get(2));
}
Логика кода следующая:
- Код 1 — это первый уровень нулевой логики, который используется для определения того, является ли элемент нулевым;
- Код 2 — это второй уровень нулевой логики, который используется для определения того, является ли условное поле элемента нулевым;
- Код 3 - это условие
Comparator
, используется здесьComparator.naturalOrder()
, потому что с помощьюString
sort, который также может быть записан какString::compareTo
. Если это сложное суждение, вы можете определить более сложноеComparator
, комбинированный режим настолько прост в использовании, что одного слоя недостаточно для установки другого слоя.
Вывод в конце статьи
В этой статье демонстрируется использование лямбда-выражений для реализации различной логики сортировки в Java 8. Новый синтаксический сахар действительно ароматный.
Зеленые холмы не изменятся, зеленая вода продолжит течь, увидимся в следующий раз.
Рекомендуемое чтение
- В этой статье описаны 24 операции Collectors в Java8 Stream.
- Статья для освоения 6 операций необязательного Java8.
- Используйте лямбда-выражения для реализации функций суперсортировки.
- Библиотека времени Java8 (1): введение в класс времени и общие API-интерфейсы в Java8.
- Библиотека времени Java8 (2): преобразование даты в LocalDate или LocalDateTime
- Библиотека времени Java8 (3): начало работы с классами времени в Java8
- Библиотека времени Java8 (4): проверьте, является ли строка даты допустимой
- Что нового в Java8
- Что нового в Java9
Привет, я смотрю на гору. Плавайте в мире кода, играйте и наслаждайтесь жизнью. Если статья была вам полезна, ставьте лайк, добавляйте в закладки и подписывайтесь.