
Функциональный стиль, представленный в Java 8, помогает нам уменьшить пропасть между бизнес-логикой и кодом. Он позволяет нам рассказывать историю в естественном потоке на более высоком уровне. Вместо того, чтобы говорить как вы хотите это сделать, вы можете сказать что вы хотите сделать.
Код становится более чистым и кратким.
Функции высокого порядка позволяют нам:
- Отправлять функции в другие функции
- Создавать функции внутри других функций
- Возвращать функции из других функций
Это большая победа для Java, где для этого нам нужно отправлять, создавать и возвращать объекты. Мы сможем писать код, который будет более надёжный, сосредоточенный и более лёгкий для повторного использования.
Благодаря лямбдам мы можем делать ленивые вычисления. Когда лямбда-выражение отправляется как аргумент метода, компилятор вычислит его, когда оно вызывается в методе. Это отличается от обычных аргументов методов, которые вычисляются сразу же.
Лямбды делают написание unit-тестов весёлым. Они позволяют нам создавать легковесные тесты, которые чисты, малы по размеру и быстры в написании. Мы можем корчевать тестируемый код, используя лямбды. Это позволяет нам тестировать, как все виды сценариев повлияют на код.
Новые паттерны для изучения.
И многое другое!
break
, continue
, return
резко меняют поведение цикла, заставляя нас понимать не только, чего код пытается достигнуть, но и также понимать, как работает цикл.
Сейчас мы взглянем, как мы можем преобразовать циклы в более краткий и читабельный код.
Да начнётся кодинг!
Мы будем работать со статьями. У статьи есть название, автор и несколько тегов.
private class Article {
private final String title;
private final String author;
private final List<String> tags;
private Article(String title, String author, List<String> tags) {
this.title = title;
this.author = author;
this.tags = tags;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public List<String> getTags() {
return tags;
}
}
Каждый пример будет содержать традиционное решение с использованием циклов и решение, использующее новые фишки Java 8.
В первом примере мы хотим найти в коллекции первую статью с тегом “Java”.
Давайте взглянем на решение с использованием цикла.
public Article getFirstJavaArticle() {
for (Article article : articles) {
if (article.getTags().contains("Java")) {
return article;
}
}
return null;
}
Теперь давайте решим проблему, пользуясь операциями из Stream API.
public Optional<Article> getFirstJavaArticle() {
return articles.stream()
.filter(article -> article.getTags().contains("Java"))
.findFirst();
}
Довольно таки круто, не правда ли? Сначала мы используем операцию filter
для нахождения всех статей с тегом “Java”, потом используем findFirst()
, чтобы получить первое вхождение. Так как потоки (streams) ленивые и фильтр возвращает поток, этот подход будет обрабатывать элементы только пока не найдёт первое совпадение.
Теперь давайте получим все статьи с тегом “Java”, вместо только первой.
Сначала решение с помощью циклов.
public List<Article> getAllJavaArticles() {
List<Article> result = new ArrayList<>();
for (Article article : articles) {
if (article.getTags().contains("Java")) {
result.add(article);
}
}
return result;
}
Решение с использованием потоковых операций.
public List<Article> getAllJavaArticles() {
return articles.stream()
.filter(article -> article.getTags().contains("Java"))
.collect(Collectors.toList());
}
В этом примере мы использовали операцию collect
для сокращения результирующего потока, вместо объявления коллекции и явного добавления статей, которые подходят.
Пока всё идёт хорошо. Время для примеров, которые заставят Stream API действительно блестать.
Давайте сгруппируем все статьи по автору.
Как обычно, начинаем с решения с помощью циклов:
public Map<String, List<Article>> groupByAuthor() {
Map<String, List<Article>> result = new HashMap<>();
for (Article article : articles) {
if (result.containsKey(article.getAuthor())) {
result.get(article.getAuthor()).add(article);
} else {
ArrayList<Article> articles = new ArrayList<>();
articles.add(article);
result.put(article.getAuthor(), articles);
}
}
return result;
}
Сможем ли мы найти чистое решение этой проблемы, используя потоковые операции?
public Map<String, List<Article>> groupByAuthor() {
return articles.stream()
.collect(Collectors.groupingBy(Article::getAuthor));
}
Замечательно! Используя операцию groupingBy
и ссылку на метод getAuthor()
, мы получаем чистый и читабельный код.
Теперь давайте найдём остальные теги, используемые в коллекции.
Начнём с циклового примера:
public Set<String> getDistinctTags() {
Set<String> result = new HashSet<>();
for (Article article : articles) {
result.addAll(article.getTags());
}
return result;
}
Окей, давайте взглянем, как мы можем решить это с помощью потоковых операций:
public Set<String> getDistinctTags() {
return articles.stream()
.flatMap(article -> article.getTags().stream())
.collect(Collectors.toSet());
}
Круто! flatmap
помогает нам сгладить список тегов в один результирующий поток, а затем мы используем collect
для создания возвращаемого сета.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Функциональное программирование для всех.
Не скажу о лямбдах и функциональных возможностях (пока не разбираюсь в них), но можно для примера рассмотреть дженерики, появившиеся в джаве в 5-й версии. С ними можно писать типо-защищенные обобщенные функции, для которых ранее приходилось писать функции для Object и ловить run-time исключения (либо лишний раз думать и писать обработчики, но всего не предусмотришь). Теперь же, с этими «угловыми скобками» об ошибке расскажет уже компилятор, что в разы лучше для качества программы.
Мне очень интересна тема функциональных языков, я уже записался на отслеживание курса по Скале на курсере (курс от создателя языка), но пока практически ничего не знаю в них. Но мне кажется, возможность передавать в методы ссылки на функции — это очень круто. Если сейчас приходится извращаться и передавать объекты класса, реализующего какой-то интерфейс, то с функциональными возможностями такие штуки можно будет писать более чисто и читабельно.
А неприятие новых возможностей своих инструментов (а ЯП — это основной инструмент разработчика), по-моему, это просто в первую очередь ограничение самого себя, ведь новые знания повышают твою же ценность для работодателей против других кандидатов, такими знаниями не обладающих ;)