Тагир Валеев известен как один из главных «стримоводов» — он автор библиотеки StreamEx, множества ответов по стримам на Stack Overflow и докладов о них. Поэтому, когда разработчик и блогер Николай Парлог решительно возразил распространённому мнению о главной пользе стримов, мы решили узнать позицию Тагира.
Парлог недоволен тем, что превыше всего ставят лаконичность кода: «Я ненавижу, когда люди называют это главной мотивацией для использования стримов! Серьёзно, мы же Java-разработчики, нам не привыкать писать немного больше кода, если это повышает читабельность». По его словам, на самом деле главное достоинство стримов — в том, что с ними код становится понятнее, а количество строк при этом может даже увеличиться, если разделять операции по разным строкам ради читабельности.
Насколько Тагир согласен с Парлогом, и что он считает основным поводом использовать стримы? Вот что он нам рассказал:
«Сложный вопрос — что считать «главной» мотивацией. Мотиваций может быть несколько, и иногда лаконичность действительно играет роль. Например, код, который сворачивается в стрим с коллектором groupingBy, в Java 7 выглядит существенно длиннее. Или поиск максимума с одновременной фильтрацией.
Я согласен с Николаем, что ясность кода определённо важна. Но часто лаконичный код и оказывается более ясным, поэтому тут не очень понятно, о чём спорить.
На мой взгляд, стримы «взлетают», когда вы начинаете менять API приложения, и всякие ваши внутренние методы, которые раньше возвращали массивы и списки, начинают возвращать стримы. Тогда стримы начинают пронизывать цепочки ваших методов, что одновременно и делает код лаконичным, и при этом увеличивает быстродействие. Например, у вас раньше было такое API:
class Company { private Map<String, User> name2user = ... public List<User> getUsers() { return new ArrayList<>(name2user.values()); } public List<User> getActiveUsers() { List<User> users = new ArrayList<>(); for(User user : name2user.values()) if(user.isActive()) users.add(user); return users; } public List<String> getUserNames() { List<String> names = new ArrayList<>(); for(User user : name2user.values()) names.add(user.getName()); return names; } } class World { private List<Company> companies; public List<User> getAllActiveUsers() { List<User> users = new ArrayList<>(); for(Company company : companies) users.addAll(company.getActiveUsers()) return users; } }
А после перехода на стримы станет примерно так:
class Company { private Map<String, User> name2user = ... public Stream<User> users() { return name2user.values().stream(); // не копируем } public Stream<User> activeUsers() { return users().filter(User::isActive); // не копируем } public Stream<String> userNames() { return users().map(User::getName); // не копируем } } class World { private List<Company> companies; public Stream<User> allActiveUsers() { return companies.stream().flatMap(company::activeUsers); // не копируем } }
Как видите, методы стали лаконичнее и понятнее, а главное, мы ни разу не снимаем копию данных. Один стрим может пролезать сквозь несколько наших API-методов, дополняясь различными преобразованиями и фильтрами. Скопирует данные (возможно) тот, кто стрим запросил. И то скопирует только один раз. Может, ему копия и не нужна, а, например, он хочет найти самого молодого юзера с помощью
Stream.min(comparing(User::getAge));
В общем, на мой взгляд, стримы полезны как внутри методов, добавляя лаконичность и ясность, так и снаружи, делая удобнее API и улучшая этим быстродействие (снижая необходимость в лишних копиях)».
- Тагир ВалеевРазработчик в JetBrains, занимается статическим анализатором кода IntelliJ IDEA, инспекциями и квик-фиксами. Также он кидает патчики в OpenJDK и разрабатывает опенсорсную библиотеку StreamEx. Известен на Хабрахабре как lany, в твиттере — как @tagir_valeev.