Stream API это вершина нововведений в java 8: используя функциональные интерфейсы и лямбда-выражения Stream API предоставляет функциональный подход к обработке наборов данных. Говоря проще, Stream API позволяет решать классические задачи по обработке наборов данных более гибко и элегантно:
public final String oldSchoolGreet() { List<String> result = new ArrayList<>(); for (String s: LONG_WELCOME) { if (s.length() == WORD_LENGTH) { result.add(s); } } Iterator<String> it = result.iterator(); StringBuilder buf = new StringBuilder(); buf.append(it.next()); while (it.hasNext()) { buf.append(", "); buf.append(it.next()); } return buf.toString(); }
public final String greet() { return LONG_WELCOME .stream() .filter(s -> s.length() == WORD_LENGTH) .collect(Collectors.joining(", ")); }
Вызов stream() на Iterable создаёт объект Stream, у которого есть два типа методов — конвейерные, как filter() и терминальные, как collect(). Отличаются тем, что конвейерные методы возвращают тот же самый объект Stream, а терминальные методы возвращают любой другой тип. Истинные различия конечно же глубже: конвейерные методы не выполняются сразу, а только конфигурируют конвейер обработки, в то время как терминальные методы запускают конвейер. Из этого следует, что даже если сохранить объект Stream куда-нибудь, повторное его использование после вызова терминальной функции не допускается:
@Test(expected = IllegalStateException.class) public void testClosedStream() { Stream<String> s = Arrays.stream(new String[]{"TEST", "STREAM"}); s.count(); s.noneMatch(String::isEmpty); }
Типичное использование
Поиск в коллекции элемента, удовлетворяющего условию:
public final boolean haveHello() { return LONG_WELCOME.stream().anyMatch(s->s.equals("Hello")); }
public final boolean haveNoCrocodiles() { return LONG_WELCOME.stream().noneMatch(s->s.equals("crocodile")); }
public final boolean allStringsNotEmpty() { return LONG_WELCOME.stream().allMatch(s->!s.isEmpty()); }
Все три метода в примерах — терминальные. Их предназначение очевидно и вытекает из названия, а вот их поведение не столь очевидно. Первые два метода, anyMatch() и noneMatch(), ленивы и прервут своё выполнение, как только заданное условие выполнится, не тратя ресурсы на обход всех данных.
Получение коллекции свойств из коллекции объектов:
public Collection<String> getLogins() { return data .stream() .map(User::getLogin) .collect(Collectors.toList()); }
map() применяет лямбда-выражение к Stream и возвращает Stream такого типа, который возвращает лямбда-выражение. Поскольку это конвейерная функция, вызовов map() может быть несколько. collect() это терминальная функция, собирающая результат обработки в один объект. В данном случае используется стандартный коллектор, создающий список.
Построение коллекции из элементов другой коллекции, удовлетворяющей условию:
public Collection<String> highLevelLogins() { return data .stream() .filter(u->u.getAccessLevel()>1) .map(User::getLogin) .collect(Collectors.toSet()); }
filter() мы уже видели раньше, эта функция применяет лямбда-выражение к элементам Stream, возвращая Stream, состоящий только из элементов, для которых выражение вернуло true.