Тагир Валеев о главной пользе стримов

Тагир Валеев известен как один из главных «стримоводов» — он автор библиотеки 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 и улучшая этим быстродействие (снижая необходимость в лишних копиях)».

  1. Тагир Валеев
    Разработчик в JetBrains, занимается статическим анализатором кода IntelliJ IDEA, инспекциями и квик-фиксами. Также он кидает патчики в OpenJDK и разрабатывает опенсорсную библиотеку StreamEx. Известен на Хабрахабре как lany, в твиттере — как @tagir_valeev.
Tags from the story