JavaRush/Java блог/Архив info.javarush/Избавляемся от циклов в Java 8
KapChook
19 уровень

Избавляемся от циклов в Java 8

Статья из группы Архив info.javarush
участников
Функциональный стиль, представленный в Java 8, — большая добавка к языку. Теперь Java — это не чистое ООП, теперь это гибрид ООП и функционального программирования. Это меняет правила игры и нам нужно изменить свои ООП-мозги, чтобы вобрать в себя эти изменения. Избавляемся от циклов в Java 8 - 1Но почему мы должны принимать эти изменения? Почему должны тратить время в попытках ужиться с функциональным стилем, когда мы можем решить проблему на чистом ООП?
  • Функциональный стиль, представленный в 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 для создания возвращаемого сета.

Бесконечные возможности

Это были 4 примера, как можно заменить циклы более читабельным кодом. Обязательно ознакомьтесь с Stream API, так как эта статья только поскребла её поверхность. Освоение нового функционального стиля Java будем испытанием для ООП-разработчиков, но это испытание, которое должно быть хорошо принято. Я даже пойду дальше и скажу, что вам стоит выучить чистый функциональный язык программирования. Таким образом вы сможете полностью понять возможности и мощь, которые он предоставляет. Я думаю это поможет вам понять функциональное программирование на другом уровне. Так что осваивайте функциональное программирование, наряду со старым добрым ООП, и используйте их обоих для написания ещё более великого кода! Вольный микс из переводов двух статей — Why you should embrace functional programming in Java 8 и Swerving Away from Loops in Java 8
Комментарии (9)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
S
Уровень 22
Expert
7 апреля 2019, 17:23
Эта Java хороша - начинай сначала.
PodoBulge
Уровень 31
25 апреля 2015, 00:33
есть одна интересная книжечка Functional programming in Java (можно скачать на it-ebooks)
IvanDurov
Уровень 25
25 апреля 2015, 15:59
Если нравиться функциональное программирование и Java, то советую попробовать Scala — я попробовал и не могу оторваться — это восхитительно :)
Litle
Уровень 9
26 марта 2015, 23:46
хм, непонятен только один момент. По мне, так самый важный… ничего не сказано про распараллеливание, особенно с учетом того, что бесплатного супа больше не будет! :) Думаю легкого упоменания о strem api как то слишком мало.

Функциональная программа сразу готова к распараллеливанию без каких-либо изменений. Вам не придётся задумываться о deadlock-ах или состояниях гонки (race conditions) потому что вам не нужны блокировки! Ни один кусочек данных в функциональной программе не меняется дважды одним и тем же потоком или разными. Это означает, что вы можете легко добавить потоков к вашей программе даже не задумываясь при этом о проблемах, присущих императивным языкам.
Функциональное программирование для всех.
vladimirsencov
Уровень 40
12 марта 2015, 19:09
Java пошел по пути С++ где прикрутили все, что только можно что не очень положительно сказывается на языке увеличивая его сложность и порог вхождения. Хотя никто не заставляет использовать лямбда-исчисление и прочий матан в своих разработках. Но все равно придется понимать чужой.
EvIv
Уровень 30
12 марта 2015, 19:25
Так разве это плохо? По-моему большая гибкость языка дает ему большую выразительность. Научившись применять новые возможности можно проще и более ясно писать код, для которого ранее приходилось городить фреймворки.
Не скажу о лямбдах и функциональных возможностях (пока не разбираюсь в них), но можно для примера рассмотреть дженерики, появившиеся в джаве в 5-й версии. С ними можно писать типо-защищенные обобщенные функции, для которых ранее приходилось писать функции для Object и ловить run-time исключения (либо лишний раз думать и писать обработчики, но всего не предусмотришь). Теперь же, с этими «угловыми скобками» об ошибке расскажет уже компилятор, что в разы лучше для качества программы.
Мне очень интересна тема функциональных языков, я уже записался на отслеживание курса по Скале на курсере (курс от создателя языка), но пока практически ничего не знаю в них. Но мне кажется, возможность передавать в методы ссылки на функции — это очень круто. Если сейчас приходится извращаться и передавать объекты класса, реализующего какой-то интерфейс, то с функциональными возможностями такие штуки можно будет писать более чисто и читабельно.
А неприятие новых возможностей своих инструментов (а ЯП — это основной инструмент разработчика), по-моему, это просто в первую очередь ограничение самого себя, ведь новые знания повышают твою же ценность для работодателей против других кандидатов, такими знаниями не обладающих ;)
KapChook
Уровень 19
13 марта 2015, 23:51
Тоже заинтересовался Скалой, стал читать о ней, наткнулся на Котлин. Язык от JetBrains, похож на Скала и Цейлон, только быстрее и меньше. Пока дорабатывается, релиз намечен на середину 2015. Решил вот взять поиграться. Единственная пока проблема — мало туториалов
vladimirsencov
Уровень 40
14 марта 2015, 10:35
А еще синтаксис похож на указатель на метод класса (хотя возможно это так и есть).
Kishuomi
Уровень 22
10 марта 2015, 10:09
Что ж повышение чтабельности на лицо. Особено с гупировкой. Надеюсь с производительность дела обстоят так же.