В Data Geekery мы любим Java. И так как мы действительно входим в свободный API jOOQ и запросы DSL , мы абсолютно взволнованы тем, что Java 8 принесет в нашу экосистему. Мы пару раз писали о приятных вкусностях Java 8 , и теперь мы чувствуем, что пришло время начать новую серию блогов,…
Ява 8 Пятница
Каждую пятницу мы показываем вам пару замечательных новых функций Java 8 в виде учебника, в которых используются лямбда-выражения, методы расширения и другие замечательные вещи. Вы найдете исходный код на GitHub .
Java 8 Goodie: лямбды и сортировка
Сортировка массивов и коллекций — это отличный пример использования лямбда-выражения Java 8 по той простой причине, что Comparator всегда был @FunctionalInterface все время с момента его появления в JDK 1.2. Теперь мы можем предоставлять Comparators в форме лямбда-выражения различным методам sort() .
В следующих примерах мы будем использовать этот простой класс Person :
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
static class Person { final String firstName; final String lastName; Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } @Override public String toString() { return "Person{" + "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + '}'; }} |
Очевидно, что мы могли бы добавить естественную сортировку в Person , позволяя ему реализовывать Comparable , но давайте сосредоточимся на внешних Comparators . Рассмотрим следующий список Person , чьи имена генерируются с помощью онлайн-генератора случайных имен:
|
1
2
3
4
5
6
7
8
|
List<Person> people =Arrays.asList( new Person("Jane", "Henderson"), new Person("Michael", "White"), new Person("Henry", "Brighton"), new Person("Hannah", "Plowman"), new Person("William", "Henderson")); |
Мы, вероятно, хотим отсортировать их по фамилии, а затем по имени.
Сортировка с помощью Java 7
«Классический» пример такого Comparator Java 7:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
people.sort(new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { int result = o1.lastName.compareTo(o2.lastName); if (result == 0) result = o1.firstName.compareTo(o2.firstName); return result; }});people.forEach(System.out::println); |
И вышесказанное даст:
|
1
2
3
4
5
|
Person{firstName='Henry', lastName='Brighton'}Person{firstName='Jane', lastName='Henderson'}Person{firstName='William', lastName='Henderson'}Person{firstName='Hannah', lastName='Plowman'}Person{firstName='Michael', lastName='White'} |
Сортировка с помощью Java 8
Теперь давайте переведем вышеприведенный код в эквивалентный код Java 8:
|
1
2
3
4
5
6
7
8
|
Comparator<Person> c = (p, o) -> p.lastName.compareTo(o.lastName);c = c.thenComparing((p, o) -> p.firstName.compareTo(o.firstName));people.sort(c);people.forEach(System.out::println); |
Результат, очевидно, тот же. Как читать выше? Сначала мы назначаем лямбда-выражение локальной переменной Person Comparator :
|
1
2
|
Comparator<Person> c = (p, o) -> p.lastName.compareTo(o.lastName); |
В отличие от Scala, C # или Ceylon, которые знают вывод типа из выражения к объявлению локальной переменной через ключевое слово val (или подобное), Java выполняет вывод типа из объявления переменной (или параметра, члена) к назначаемому выражению.
Другими, более неформальными словами, вывод типа выполняется «слева направо», а не «справа налево». Это делает цепочку Comparators немного громоздкой, так как компилятор Java не может задерживать вывод типов для лямбда-выражений, пока вы не передадите компаратор методу sort() .
Однако после того, как мы присвоили Comparator переменной, мы можем плавно thenComparing() другие компараторы с помощью thenComparing() :
|
1
2
|
c = c.thenComparing((p, o) -> p.firstName.compareTo(o.firstName)); |
И, наконец, мы передаем его в новый метод sort() List sort() , который является методом по умолчанию, реализованным непосредственно в интерфейсе List :
|
1
2
3
|
default void sort(Comparator<? super E> c) { Collections.sort(this, c);} |
Обходной путь для вышеуказанного ограничения
Хотя «ограничения» вывода типов в Java могут оказаться немного разочаровывающими, мы можем обойти вывод типов, создав универсальный IdentityComparator :
|
1
2
3
4
5
|
class Utils { static <E> Comparator<E> compare() { return (e1, e2) -> 0; }} |
С помощью вышеуказанного метода compare() мы можем написать следующую цепочку беглых компараторов:
|
1
2
3
4
5
6
7
8
9
|
people.sort( Utils.<Person>compare() .thenComparing((p, o) -> p.lastName.compareTo(o.lastName)) .thenComparing((p, o) -> p.firstName.compareTo(o.firstName)));people.forEach(System.out::println); |
Извлечение ключей
Это может стать еще лучше. Поскольку мы обычно сравниваем одно и то же значение POJO / DTO из обоих аргументов Comparator , мы можем предоставить их новым API через функцию «извлечения ключа». Вот как это работает:
|
1
2
3
4
|
people.sort(Utils.<Person>compare() .thenComparing(p -> p.lastName) .thenComparing(p -> p.firstName));people.forEach(System.out::println); |
Итак, учитывая Person p мы предоставляем API функцию извлечения, например, p.lastName . И на самом деле, когда мы используем ключевые экстракторы, мы можем опустить наш собственный служебный метод, поскольку в библиотеках также есть метод comparing() для запуска всей цепочки:
|
1
2
3
4
|
people.sort( Comparator.comparing((Person p) -> p.lastName) .thenComparing(p -> p.firstName));people.forEach(System.out::println); |
Опять же, нам нужно помочь компилятору, так как он не может вывести все типы, даже если в принципе метод sort() предоставит достаточно информации в этом случае. Чтобы узнать больше об обобщенном выводе типов в Java 8, см. Наш предыдущий пост в блоге .
Вывод
Как и в Java 5, самые большие улучшения в обновлении можно увидеть в библиотеках JDK. Когда Java 5 принесла безопасность типов в Comparators , Java 8 облегчает их чтение и запись (дает или принимает причуду вывода нечетного типа).
Java 8 собирается революционизировать способ программирования, и на следующей неделе мы увидим, как Java 8 влияет на то, как мы взаимодействуем с SQL.