Используйте лямбда-выражения для реализации функций суперсортировки.

Java задняя часть
Используйте лямбда-выражения для реализации функций суперсортировки.

«Это 9-й день моего участия в первом испытании обновлений 2022 года. Подробную информацию о мероприятии см.:Вызов первого обновления 2022 г."

Привет, я смотрю на гору.

Эта статья была«Продвинутая Java»В колонке вы можете получить исходный код, ответив на «java» на официальном аккаунте «The Lodge in the Mountain».

В процессе разработки системы сортировка данных является очень распространенным сценарием. Вообще говоря, мы можем сделать это двумя способами:

  1. Благодаря функции сортировки системы хранения (поддерживаемой SQL, NoSQL и NewSQL) результатом запроса является отсортированный результат.
  2. Результаты запроса представляют собой неупорядоченные данные, отсортированные в памяти.

Сегодня я хочу рассказать о втором методе сортировки, реализующем сортировку данных в памяти.

Сначала мы определяем базовый класс, а позже покажем, как выполнять сортировку в памяти на основе этого базового класса.

@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Случай:

  1. Элемент в списке является нулевым
  2. Поле элемента списка, участвующего в условии сортировки, равно 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. Код 1 — это первый уровень нулевой логики, который используется для определения того, является ли элемент нулевым;
  2. Код 2 — это второй уровень нулевой логики, который используется для определения того, является ли условное поле элемента нулевым;
  3. Код 3 - это условиеComparator, используется здесьComparator.naturalOrder(), потому что с помощьюStringsort, который также может быть записан какString::compareTo. Если это сложное суждение, вы можете определить более сложноеComparator, комбинированный режим настолько прост в использовании, что одного слоя недостаточно для установки другого слоя.

Вывод в конце статьи

В этой статье демонстрируется использование лямбда-выражений для реализации различной логики сортировки в Java 8. Новый синтаксический сахар действительно ароматный.

Зеленые холмы не изменятся, зеленая вода продолжит течь, увидимся в следующий раз.

Рекомендуемое чтение


Привет, я смотрю на гору. Плавайте в мире кода, играйте и наслаждайтесь жизнью. Если статья была вам полезна, ставьте лайк, добавляйте в закладки и подписывайтесь.