Stream API это вершина нововведений в java 8: используя функциональные интерфейсы и лямбда-выражения Stream API предоставляет функциональный подход к обработке наборов данных. Говоря проще, Stream API позволяет решать классические задачи по обработке наборов данных более гибко и элегантно:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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();
}
|
1
2
3
4
5
6
|
public final String greet() {
return LONG_WELCOME
.stream()
.filter(s -> s.length() == WORD_LENGTH)
.collect(Collectors.joining(", "));
}
|
Вызов stream() на Iterable создаёт объект Stream, у которого есть два типа методов — конвейерные, как filter() и терминальные, как collect(). Отличаются тем, что конвейерные методы возвращают тот же самый объект Stream, а терминальные методы возвращают любой другой тип. Истинные различия конечно же глубже: конвейерные методы не выполняются сразу, а только конфигурируют конвейер обработки, в то время как терминальные методы запускают конвейер. Из этого следует, что даже если сохранить объект Stream куда-нибудь, повторное его использование после вызова терминальной функции не допускается:
1
2
3
4
5
6
7
|
@Test(expected = IllegalStateException.class)
public void testClosedStream() {
Stream<String> s = Arrays.stream(new String[]{"TEST", "STREAM"});
s.count();
s.noneMatch(String::isEmpty);
}
|
Типичное использование
Поиск в коллекции элемента, удовлетворяющего условию:
1
2
3
|
public final boolean haveHello() {
return LONG_WELCOME.stream().anyMatch(s->s.equals("Hello"));
}
|
1
2
3
|
public final boolean haveNoCrocodiles() {
return LONG_WELCOME.stream().noneMatch(s->s.equals("crocodile"));
}
|
1
2
3
|
public final boolean allStringsNotEmpty() {
return LONG_WELCOME.stream().allMatch(s->!s.isEmpty());
}
|
Все три метода в примерах — терминальные. Их предназначение очевидно и вытекает из названия, а вот их поведение не столь очевидно. Первые два метода, anyMatch() и noneMatch(), ленивы и прервут своё выполнение, как только заданное условие выполнится, не тратя ресурсы на обход всех данных.
Получение коллекции свойств из коллекции объектов:
1
2
3
4
5
6
|
public Collection<String> getLogins() {
return data
.stream()
.map(User::getLogin)
.collect(Collectors.toList());
}
|
map() применяет лямбда-выражение к Stream и возвращает Stream такого типа, который возвращает лямбда-выражение. Поскольку это конвейерная функция, вызовов map() может быть несколько. collect() это терминальная функция, собирающая результат обработки в один объект. В данном случае используется стандартный коллектор, создающий список.
Построение коллекции из элементов другой коллекции, удовлетворяющей условию:
1
2
3
4
5
6
7
|
public Collection<String> highLevelLogins() {
return data
.stream()
.filter(u->u.getAccessLevel()>1)
.map(User::getLogin)
.collect(Collectors.toSet());
}
|
filter() мы уже видели раньше, эта функция применяет лямбда-выражение к элементам Stream, возвращая Stream, состоящий только из элементов, для которых выражение вернуло true.
Продолжение следует. Код примера доступен на github.