[Перевод] Java 8: Овладейте новым уровнем абстракции
Одной из многих причин, почему мне нравится работать именно с функциональным программированием, является высокий уровень абстракции. Это связано с тем, что в конечном итоге мы имеем дело с более читаемым и лаконичным кодом, что, несомненно, способствует сближению с логикой предметной области.В данной статье большее внимание уделяется на четыре вещи, представленные в Java 8, которые помогут вам овладеть новым уровнем абстракции.
1. Больше никаких цикловЯ уже говорил это ранее, и скажу снова. Попрощайтесь с циклами, и приветствуйте Stream API. Дни обсуждения Java о циклах элементов подходят к концу. Со Stream API в Java мы можем сказать что мы хотим получить, вместо того чтобы говорить как этого можно добиться.Давайте рассмотрим следующий пример.
Мы имеем список статей, каждая из которых имеет свой список тегов. Теперь мы хотим получить первую статью, содержащую тег «Java».
Взглянем на стандартный подход.
public Article getFirstJavaArticle () { for (Article article: articles) { if (article.getTags ().contains («Java»)) { return article; } } return null; } Решим задачу, используя Stream API.
public Optional
Сначала мы используем filter, чтобы найти все статьи, которые содержат тег Java, далее с помощью findFirst получаем первое включение.
Возникает вопрос: почему мы должны фильтровать весь список, если нам необходимо только первое включение? Так как потоки… ленивы и filter возвращает поток, вычисления происходят до тех пор, пока не будет найдено первое включение.
Я уже ранее посвятил статью о замене циклов на stream API. Прочтите его, если вам нужно больше примеров.
2. Избавьтесь от null-проверок
Можно заметить, что в предыдущем примере мы можем вернуть Optional
Этот объект имеет некоторые функции высшего порядка, избавляющие от добавления повторяющихся if null/notNull проверок, что позволяет нам сфокусироваться на том, что мы хотим сделать.
Теперь усовершенствуем метод getFirstJavaArticle. Если не найдётся Java-статья, мы будем рады получению последней статьи.
Давайте рассмотрим, как выглядит типичное решение.
Article article = getFirstJavaArticle ();
if (article == null) {
article = fetchLatestArticle ();
}
А теперь решение с использованием Optional
getFirstJavaArticle () .orElseGet (this: fetchLatestArticle); Отлично выглядит, не правда ли?
Ни лишних переменных, ни if-конструкций, ни каких-либо упоминаний null. Мы просто используем Optional.orElseGet, чтобы сказать, что мы хотим получить, если не будет найдено значение.
Взглянем на другой пример использования Optional. Предположим, мы хотим получить название первой Java-статьи, если она будет найдена.
Опять же, используя типичное решение, нам пришлось бы добавлять null-проверку, но… знаете что? Optional здесь, чтобы спасти этот день.
playGround.getFirstJavaArticle () .map (Article: getTitle); Как вы можете видеть, Optional реализует функцию высшего порядка map, помогая нам применить функцию к результату, если он есть.
Для большей информации об Optional смотрите документацию.
3. Создайте свои функции высшего порядка Как видите, Java 8 поставляется с уже готовым набором функций высшего порядка, и вы можете с ними творить чудеса. Но зачем останавливаться? И почему бы не создать свои собственные функции высшего порядка? Единственное, что нужно, чтобы сделать функцию высшего порядка, — это взять один из функциональных интерфейсов Java, или интерфейса SAM-типа в качестве аргумента и / или возвращать одно из них.
Чтобы проиллюстрировать это, рассмотрим следующий сценарий.
У нас есть принтер, который может печатать различные виды документов. Перед печатью принтер должен прогреться, а после печати перейти в спящий режим.
Теперь мы хотим получить возможность посылать команды на принтер без заботы о его процедурах включения и выключения. Это может быть решено путём создания функции высшего порядка.
public void print (Consumer
printer.prepare ();
toPrint.accept (printer);
printer.sleep ();
}
Как видите, мы используем Consumer
Теперь мы можем с легкостью использовать наш принтер, не заботясь ни о чём другом, кроме того, что мы хотим распечатать.
// Распечатать одну статью printHandler.print (p → p.print (oneArticle));
// Распечатать несколько статей printHandler.print (p → allArticles.forEach (p: print)); Для более подробного примера прочтите мою статью о том, как создать TransactionHandler.
4. Опасайтесь дублирования. Принцип DRY Написание функций — дело легкое и быстрое. Тем не менее, с лёгким написанием кода приходит желание дублирования.Рассмотрим следующий пример.
public Optional
Весьма заманчивым является создавать новые потоки. Они настолько малы и их так просто можно сделать, как же это может навредить? Напротив, небольшие участки кода должны мотивировать принцип DRY дальше.
Давайте реорганизуем наш код. Для начала сделаем нашу функцию getFirstJavaArticle более универсальной — она будет принимать предикат в качестве аргумента, чтобы отфильтровать статьи в соответствии с тем, что нам нужно.
public Optional
getFirst (article → article.getTags ().contains («Java»)); getFirst (article → article.getTags ().contains («Scala»)); getFirst (article → article.getTitle ().contains («Clojure»)); И, тем не менее, мы всё ещё имеем дело с повторяющимся кодом. Вы можете видеть, что используется один и тот же предикат для различных значений. Давайте попробуем удалить эти дублирования посредством интерфейса Function. Теперь наш код выглядит следующим образом.
Function
getFirst (basedOnTag.apply («Java»)); getFirst (basedOnTag.apply («Scala»)); Отлично! Я бы сказал, что этот код соответствует принципу DRY, не так ли?
Я надеюсь, что эта статья оказалась для вас полезной и придала несколько идей относительно того, что бы вы могли сделать на более высоком уровне абстракции с использованием Java 8 и функциональных особенностей.