Статьи

Функции гуавы и лямбды на Java 8

Недавно я прочитал « Состояние лямбды» Брайана Гетца, и после прочтения этой статьи я хотел попробовать использовать лямбда-выражения Java 8. В своей статье Брайан продолжает описывать интерфейсы, которые имеют один метод, как «функциональные» интерфейсы. Функциональные интерфейсы почти всегда используются в качестве анонимных классов, причем каноническим примером является ActionListener. Эти «функциональные» интерфейсы являются исходной целью для лямбда-выражений, главной целью которых является удаление большей части шаблонного или церемония вокруг их использования. Когда я писал серию статей для библиотеки Guava, интерфейс Function сразу же пришел мне на ум в качестве основного кандидата для использования лямбда-выражений.

Моя цель проста: взять модульный тест из моего блога Guava Futures (он интенсивно использует интерфейс Function) и преобразовать его в лямбда-выражения. Хотя я опишу структуру выражения lamba, поскольку оно относится к представленному примеру, этот пост не является руководством по лямбда-выражениям Java 8. Скорее это документирует мои первые попытки использования лямбда-выражений в Java.

Лямбда-выражения

Первый пример — тест для метода Futures.chain, который принимает функцию в качестве одного из аргументов:

01
02
03
04
05
06
07
08
09
10
11
12
Function<List<String>, ListenableFuture<List<Person>>> queryFunction =
      new Function<List<String>, ListenableFuture<List<Person>>>() {
            @Override
            public ListenableFuture<List<Person>> apply(final List<String> ids) {
                 return dbService.getPersonsByIdAsync(ids);
            }
        };
 
ListenableFuture<List<String>> indexSearch =
      luceneSearcher.searchAsync('firstName:martin');
ListenableFuture<List<Person>> results =
      Futures.chain(indexSearch,queryFunction,executorService);

Используя выражения lamba, теперь это будет выглядеть так:

1
2
3
4
5
6
7
Function<List<String>, ListenableFuture<List<Person>>> queryFunction =
      ids ->(dbService.getPersonsByIdAsync(ids));
 
  ListenableFuture<List<String>> indexSearch =
      luceneSearcher.searchAsync('firstName:martin');
  ListenableFuture<List<Person>>results =
      Futures.chain(indexSearch, queryFunction,executorService);

Имейте в виду, что из приведенных выше примеров кода два выделенных раздела эквивалентны. Давайте посмотрим на строку 2 и объясним, как это совпадает со строками 1-7 из первого примера кода.

  1. ids — это входные данные для метода apply и соответствует final List<String> ids параметру final List<String> ids в строке 4 в первом примере кода. Типы выражения lamba выводятся из контекста, в котором оно используется, поэтому нам не нужно повторять их для параметра ids .
  2. Затем есть маркер стрелки (->), который является частью общего синтаксиса лямбда-выражений Java 8 в их текущей форме.
  3. Затем у нас есть тело лямбды (dbService.getPersonsByIdAsync (ids)), которое является вызовом метода, который возвращает ListenableFuture, который в свою очередь выдает объекты List of Person. Обратите внимание, что нам не нужно было помещать оператор return, так как это одно выражение, которое вычисляется и возвращается.

Следующий пример — это служебный метод из теста, который возвратил ListenableFutures, передав анонимные экземпляры Callable в ExecutorService:

01
02
03
04
05
06
07
08
09
10
11
12
13
private ListenableFuture<List<Person>> getPersonsByFirstNameFuture(final String firstName, final boolean error) {
return executorService.submit(new Callable<List<Person>>() {
            @Override
            public List<Person> call() throws Exception {
                startSignal.await();
                if (error) {
                    throw new RuntimeException('Ooops!');
                }
                List<String> ids = luceneSearcher.search('firstName:' + firstName);
                return dbService.getPersonsById(ids);
            }
        });
}

А вот эквивалент с использованием лямбда-выражения:

1
2
3
4
5
6
7
8
9
private ListenableFuture<List<Person>> getPersonsByFirstNameFuture(final String firstName, final boolean error) {
 return executorService.submit(() -> {startSignal.await();
                                       if (error) {
                                        throw new RuntimeException('Ooops!');
                                       }
                                     List<String> ids = luceneSearcher.search('firstName:' + firstName);
                                     return dbService.getPersonsById(ids);
                                  });
}

В этом примере нет входных аргументов, поэтому выражение начинается с пустых скобок () в строке 2. Существует токен ->, но в этом примере тело содержит несколько операторов, окруженных {…}. Поскольку существует несколько операторов, в строке 7 требуется явный оператор возврата.

Среда для запуска Java 8

Мой текущий ноутбук — MacBook Pro, поэтому мне нужно было настроить среду для запуска Java 8 с поддержкой лямбды. Вот шаги, которые я предпринял:

  1. Установил LinuxMint 12 на VirtualBox.
  2. Создал каталог и поделился им с гостем LinuxMint
  3. Установленный разработчиком предварительный просмотр java 8.
  4. Чтобы получить исходный код и исходный текст теста из существующего проекта maven, я запустил mvn jar:jar jar:test-jar и поместил полученные файлы jar в общую директорию
  5. Поместил все зависимости в общий каталог (guava, lucene, h2 и junit)
  6. Переписал модульный тест для использования лямбд и запустил новый тест из командной строки

Вывод

Хотя до выхода Java 8 с поддержкой лямбды пройдет некоторое время, то, что доступно в предварительной версии для разработчиков, выглядит многообещающе. Спасибо за ваше время и, как всегда, приветствуются комментарии и предложения.

Ресурсы

Справка: функции Guava и Java 8 Lambdas от нашего партнера по JCG Билла Бекака в блоге Randomечет о кодировании .