Статьи

Ява 8 пятничных вкусностей: лямбды и сортировка

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

Ссылка: Java 8 Friday Goodies: лямбды и сортировка от нашего партнера по JCG Лукаса Эдера в блоге JAVA, SQL и AND JOOQ .