В 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.