Шпаргалка Java программиста 4. Java Stream API
Несмотря на то, что Java 8 вышла уже достаточно давно, далеко не все программисты используют её новые возможности, кого-то останавливает то, что рабочие проекты слишком сложно перевести с Java 7 или даже Java 6, кого-то использование в своих проектах GWT, кто-то делает проекты под Android и не хочет или не может использовать сторонние библиотеки для реализации лямбд и Stream Api. Однако знание лямбд и Stream Api для программиста Java зачастую требуют на собеседованиях, ну и просто будет полезно при переходе на проект где используется Java 8. Я хотел бы предложить вам краткую шпаргалку по Stream Api с практическими примерами реализации различных задач с новым функциональным подходом. Знания лямбд и функционального программирования не потребуется (я постарался дать примеры так, чтобы все было понятно), уровень от самого базового знания Java и выше.
Также, так как это шпаргалка, статья может использоваться, чтобы быстро вспомнить как работает та или иная особенность Java Stream Api. Краткое перечисление возможностей основных функций дано в начале статьи.
Stream Api позволяет писать обработку структур данных в стиле SQL, то если раньше задача получить сумму всех нечетных чисел из коллекции решалась следующим кодом:
Integer sumOddOld = 0;
for(Integer i: collection) {
if(i % 2 != 0) {
sumOddOld += i;
}
}
То с помощью Stream Api можно решить такую задачу в функциональном стиле:
Integer sumOdd = collection.stream().filter(o -> o % 2 != 0).reduce((s1, s2) -> s1 + s2).orElse(0);
Более того, Stream Api позволяет решать задачу параллельно лишь изменив stream () на parallelStream () без всякого лишнего кода, т.е.
Integer sumOdd = collection.parallelStream().filter(o -> o % 2 != 0).reduce((s1, s2) -> s1 + s2).orElse(0);
Уже делает код параллельным, без всяких семафоров, синхронизаций, рисков взаимных блокировок и т.п.
Давайте начнем с начала, а именно с создания объектов stream в Java 8.
I. Способы создания стримов
Перечислим несколько способов создать стрим
Способ создания стрима | Шаблон создания | Пример |
---|---|---|
1. Классический: Создание стрима из коллекции | collection.stream() |
|
2. Создание стрима из значений | Stream.of(значение1, … значениеN) |
|
3. Создание стрима из массива | Arrays.stream(массив) |
|
4. Создание стрима из файла (каждая строка в файле будет отдельным элементом в стриме) | Files.lines(путь_к_файлу) |
|
5. Создание стрима из строки | «строка».chars() |
|
6. С помощью Stream.builder | Stream.builder().add(…)…build() |
|
7. Создание параллельного стрима | collection.parallelStream() |
|
8. Создание бесконечных стрима с помощью Stream.iterate |
Stream.iterate(начальное_условие, выражение_генерации) |
|
9. Создание бесконечных стрима с помощью Stream.generate | Stream.generate(выражение_генерации) |
|
В принципе, кроме последних двух способов создания стрима, все не отличается от обычных способов создания коллекций. Последние два способа служат для генерации бесконечных стримов, в iterate задается начальное условие и выражение получение следующего значения из предыдущего, то есть Stream.iterate (1, n → n + 1) будет выдавать значения 1, 2, 3, 4, … N. Stream.generate служит для генерации константных и случайных значений, он просто выдает значения соответствующие выражению, в данном примере, он будет выдавать бесконечное количество значений «a1».
Выражение n → n + 1, это просто аналог выражения Integer func (Integer n) { return n+1;}, а выражение () → «a1» аналог выражения String func () { return «a1»;} обернутых в анонимный класс.
System.out.println("Test buildStream start");
// Создание стрима из значений
Stream streamFromValues = Stream.of("a1", "a2", "a3");
System.out.println("streamFromValues = " + streamFromValues.collect(Collectors.toList())); // напечатает streamFromValues = [a1, a2, a3]
// Создание стрима из массива
String[] array = {"a1","a2","a3"};
Stream streamFromArrays = Arrays.stream(array);
System.out.println("streamFromArrays = " + streamFromArrays.collect(Collectors.toList())); // напечатает streamFromArrays = [a1, a2, a3]
Stream streamFromArrays1 = Stream.of(array);
System.out.println("streamFromArrays1 = " + streamFromArrays1.collect(Collectors.toList())); // напечатает streamFromArrays = [a1, a2, a3]
// Создание стрима из файла (каждая запись в файле будет отдельной строкой в стриме)
File file = new File("1.tmp");
file.deleteOnExit();
PrintWriter out = new PrintWriter(file);
out.println("a1");
out.println("a2");
out.println("a3");
out.close();
Stream streamFromFiles = Files.lines(Paths.get(file.getAbsolutePath()));
System.out.println("streamFromFiles = " + streamFromFiles.collect(Collectors.toList())); // напечатает streamFromFiles = [a1, a2, a3]
// Создание стрима из коллекции
Collection collection = Arrays.asList("a1", "a2", "a3");
Stream streamFromCollection = collection.stream();
System.out.println("streamFromCollection = " + streamFromCollection.collect(Collectors.toList())); // напечатает streamFromCollection = [a1, a2, a3]
// Создание числового стрима из строки
IntStream streamFromString = "123".chars();
System.out.print("streamFromString = ");
streamFromString.forEach((e)->System.out.print(e + " , ")); // напечатает streamFromString = 49 , 50 , 51 ,
System.out.println();
// С помощью Stream.builder
Stream.Builder builder = Stream.builder();
Stream streamFromBuilder = builder.add("a1").add("a2").add("a3").build();
System.out.println("streamFromBuilder = " + streamFromBuilder.collect((Collectors.toList()))); // напечатает streamFromFiles = [a1, a2, a3]
// Создание бесконечных стримов
// С помощью Stream.iterate
Stream streamFromIterate = Stream.iterate(1, n -> n + 2);
System.out.println("streamFromIterate = " + streamFromIterate.limit(3).collect(Collectors.toList())); // напечатает streamFromIterate = [1, 3, 5]
// С помощью Stream.generate
Stream streamFromGenerate = Stream.generate(() -> "a1");
System.out.println("streamFromGenerate = " + streamFromGenerate.limit(3).collect(Collectors.toList())); // напечатает streamFromGenerate = [a1, a1, a1]
// Создать пустой стрим
Stream streamEmpty = Stream.empty();
System.out.println("streamEmpty = " + streamEmpty.collect(Collectors.toList())); // напечатает streamEmpty = []
// Создать параллельный стрим из коллекции
Stream parallelStream = collection.parallelStream();
System.out.println("parallelStream = " + parallelStream.collect(Collectors.toList())); // напечатает parallelStream = [a1, a2, a3]
II. Методы работы со стримами
Java Stream API предлагает два вида методов:
1. Конвейерные — возвращают другой stream, то есть работают как builder,
2. Терминальные — возвращают другой объект, такой как коллекция, примитивы, объекты, Optional и т.д.
В целом, этот механизм похож на конструирования SQL запросов, может быть сколько угодно вложенных Select’ов и только один результат в итоге. Например, в выражении collection.stream ().filter ((s) → s.contains (»1»)).skip (2).findFirst (), filter и skip — конвейерные, а findFirst — терминальный, он возвращает объект Optional и это заканчивает работу со stream’ом.
2.1 Краткое описание конвейерных методов работы со стримами
Метод stream | Описание | Пример |
---|---|---|
filter | Отфильтровывает записи, возвращает только записи, соответствующие условию | collection.stream ().filter («a1»:: equals).count () |
skip | Позволяет пропустить N первых элементов | collection.stream ().skip (collection.size () — 1).findFirst ().orElse (»1») |
distinct | Возвращает стрим без дубликатов (для метода equals) | collection.stream ().distinct ().collect (Collectors.toList ()) |
map | Преобразует каждый элемент стрима | collection.stream ().map ((s) → s + »_1»).collect (Collectors.toList ()) |
peek | Возвращает тот же стрим, но применяет функцию к каждому элементу стрима | collection.stream ().map (String: toUpperCase).peek ((e) → System.out.print (»,» + e)). collect (Collectors.toList ()) |
limit | Позволяет ограничить выборку определенным количеством первых элементов | collection.stream ().limit (2).collect (Collectors.toList ()) |
sorted | Позволяет сортировать значения либо в натуральном порядке, либо задавая Comparator | collection.stream ().sorted ().collect (Collectors.toList ()) |
mapToInt, mapToDouble, mapToLong |
Аналог map, но возвращает числовой стрим (то есть стрим из числовых примитивов) | collection.stream ().mapToInt ((s) → Integer.parseInt (s)).toArray () |
flatMap, flatMapToInt, flatMapToDouble, flatMapToLong |
Похоже на map, но может создавать из одного элемента несколько | collection.stream ().flatMap ((p) → Arrays.asList (p.split (»,»)).stream ()).toArray (String[]:: new) |
2.2 Краткое описание терминальных методов работы со стримами
Метод stream | Описание | Пример |
---|---|---|
findFirst | Возвращает первый элемент из стрима (возвращает Optional) | collection.stream ().findFirst ().orElse (»1») |
findAny | Возвращает любой подходящий элемент из стрима (возвращает Optional) | collection.stream ().findAny ().orElse (»1») |
collect | Представление результатов в виде коллекций и других структур данных | collection.stream ().filter ((s) → s.contains (»1»)).collect (Collectors.toList ()) |
count | Возвращает количество элементов в стриме | collection.stream ().filter («a1»:: equals).count () |
anyMatch | Возвращает true, если условие выполняется хотя бы для одного элемента | collection.stream ().anyMatch («a1»:: equals) |
noneMatch | Возвращает true, если условие не выполняется ни для одного элемента | collection.stream ().noneMatch («a8»:: equals) |
allMatch | Возвращает true, если условие выполняется для всех элементов | collection.stream ().allMatch ((s) → s.contains (»1»)) |
min | Возвращает минимальный элемент, в качестве условия использует компаратор | collection.stream ().min (String: compareTo).get () |
max | Возвращает максимальный элемент, в качестве условия использует компаратор | collection.stream ().max (String: compareTo).get () |
forEach | Применяет функцию к каждому объекту стрима, порядок при параллельном выполнении не гарантируется | set.stream ().forEach ((p) → p.append (»_1»)); |
forEachOrdered | Применяет функцию к каждому объекту стрима, сохранение порядка элементов гарантирует | list.stream ().forEachOrdered ((p) → p.append (»_new»)); |
toArray | Возвращает массив значений стрима | collection.stream ().map (String: toUpperCase).toArray (String[]:: new); |
Обратите внимание методы findFirst, findAny, anyMatch это short-circuiting методы, то есть обход стримов организуется таким образом чтобы найти подходящий элемент максимально быстро, а не обходить весь изначальный стрим.
2.3 Краткое описание дополнительных методов у числовых стримов
Метод stream | Описание | Пример |
---|---|---|
sum | Возвращает сумму всех чисел | collection.stream ().mapToInt ((s) → Integer.parseInt (s)).sum () |
average | Возвращает среднее арифметическое всех чисел | collection.stream ().mapToInt ((s) → Integer.parseInt (s)).average () |
mapToObj | Преобразует числовой стрим обратно в объектный | intStream.mapToObj ((id) → new Key (id)).toArray () |
2.4 Несколько других полезных методов стримов
Метод stream | Описание |
---|---|
isParallel | Узнать является ли стрим параллельным |
parallel | Вернуть параллельный стрим, если стрим уже параллельный, то может вернуть самого себя |
sequential | Вернуть последовательный стрим, если стрим уже последовательный, то может вернуть самого себя |
С помощью, методов parallel и sequential можно определять какие операции могут быть параллельными, а какие только последовательными. Так же из любого последовательного стрима можно сделать параллельный и наоборот, то есть:
collection.stream().
peek(...). // операция последовательна
parallel().
map(...). // операция может выполняться параллельно,
sequential().
reduce(...) // операция снова последовательна
Внимание: крайне не рекомендуется использовать параллельные стримы для сколько-нибудь долгих операций (получение данных из базы, сетевых соединений), так как все параллельные стримы работают c одним пулом fork/join и такие долгие операции могут остановить работу всех параллельных стримов в JVM.
III. Примеры работы с методами стримов
Рассмотрим работу с методами на различных задачах, обычно требующихся при работе с коллекциями.
3.1 Примеры использования filter, findFirst, findAny, skip, limit и count
Условие: дана коллекция строк Arrays.asList («a1», «a2», «a3», «a1»), давайте посмотрим как её можно обрабатывать используя методы filter, findFirst, findAny, skip и count:
Задача | Код примера | Результат |
---|---|---|
Вернуть количество вхождений объекта «a1» | collection.stream ().filter («a1»:: equals).count () | 2 |
Вернуть первый элемент коллекции или 0, если коллекция пуста | collection.stream ().findFirst ().orElse (0) | a1 |
Вернуть последний элемент коллекции или «empty», если коллекция пуста | collection.stream ().skip (collection.size () — 1).findAny ().orElse («empty») | a1 |
Найти элемент в коллекции равный «a3» или кинуть ошибку | collection.stream ().filter («a3»:: equals).findFirst ().get () | a3 |
Вернуть третий элемент коллекции по порядку | collection.stream ().skip (2).findFirst ().get () | a3 |
Вернуть два элемента начиная со второго | collection.stream ().skip (1).limit (2).toArray () | [a2, a3] |
Выбрать все элементы по шаблону | collection.stream ().filter ((s) → s.contains (»1»)).collect (Collectors.toList ()) | [a1, a1] |
Обратите внимание, что методы findFirst и findAny возвращают новый тип Optional, появившийся в Java 8, для того чтобы избежать NullPointerException. Метод filter удобно использовать для выборки лишь определенного множества значений, а метод skip позволяет пропускать определенное количество элементов.
Выражение «a3»:: equals это аналог boolean func (s) { return «a3».equals (s);}, а (s) → s.contains (»1») это аналог boolean func (s) { return s.contains (»1»);} обернутых в анонимный класс.
Условие: дана коллекция класс People (с полями name — имя, age — возраст, sex — пол), вида Arrays.asList (new People («Вася», 16, Sex.MAN), new People («Петя», 23, Sex.MAN), new People («Елена», 42, Sex.WOMEN), new People («Иван Иванович», 69, Sex.MAN)). Давайте посмотрим примеры как работать с таким классом:
Задача | Код примера | Результат |
---|---|---|
Выбрать мужчин-военнообязанных (от 18 до 27 лет) | peoples.stream ().filter ((p)→ p.getAge () >= 18 && p.getAge () < 27 && p.getSex () == Sex.MAN).collect (Collectors.toList ()) |
[{name='Петя', age=23, sex=MAN}] |
Найти средний возраст среди мужчин | peoples.stream ().filter ((p) → p.getSex () == Sex.MAN). mapToInt (People: getAge).average ().getAsDouble () |
36.0 |
Найти кол-во потенциально работоспособных людей в выборке (т.е. от 18 лет и учитывая что женщины выходят в 55 лет, а мужчина в 60) | peoples.stream ().filter ((p) → p.getAge () >= 18).filter ( (p) → (p.getSex () == Sex.WOMEN && p.getAge () < 55) || (p.getSex() == Sex.MAN && p.getAge() < 60)).count() |
2 |
// filter - возвращает stream, в котором есть только элементы, соответствующие условию фильтра
// count - возвращает количество элементов в стриме
// collect - преобразует stream в коллекцию или другую структуру данных
// mapToInt - преобразовать объект в числовой стрим (стрим, содержащий числа)
private static void testFilterAndCount() {
System.out.println();
System.out.println("Test filter and count start");
Collection collection = Arrays.asList("a1", "a2", "a3", "a1");
Collection peoples = Arrays.asList(
new People("Вася", 16, Sex.MAN),
new People("Петя", 23, Sex.MAN),
new People("Елена", 42, Sex.WOMEN),
new People("Иван Иванович", 69, Sex.MAN)
);
// Вернуть количество вхождений объекта
long count = collection.stream().filter("a1"::equals).count();
System.out.println("count = " + count); // напечатает count = 2
// Выбрать все элементы по шаблону
List select = collection.stream().filter((s) -> s.contains("1")).collect(Collectors.toList());
System.out.println("select = " + select); // напечатает select = [a1, a1]
// Выбрать мужчин-военнообязанных
List militaryService = peoples.stream().filter((p)-> p.getAge() >= 18 && p.getAge() < 27
&& p.getSex() == Sex.MAN).collect(Collectors.toList());
System.out.println("militaryService = " + militaryService); // напечатает militaryService = [{name='Петя', age=23, sex=MAN}]
// Найти средний возраст среди мужчин
double manAverageAge = peoples.stream().filter((p) -> p.getSex() == Sex.MAN).
mapToInt(People::getAge).average().getAsDouble();
System.out.println("manAverageAge = " + manAverageAge); // напечатает manAverageAge = 36.0
// Найти кол-во потенциально работоспособных людей в выборке (т.е. от 18 лет и учитывая что женщины выходят в 55 лет, а мужчина в 60)
long peopleHowCanWork = peoples.stream().filter((p) -> p.getAge() >= 18).filter(
(p) -> (p.getSex() == Sex.WOMEN && p.getAge() < 55) || (p.getSex() == Sex.MAN && p.getAge() < 60)).count();
System.out.println("peopleHowCanWork = " + peopleHowCanWork); // напечатает manAverageAge = 2
}
// findFirst - возвращает первый Optional элемент из стрима
// skip - пропускает N первых элементов (где N параметр метода)
// collect преобразует stream в коллекцию или другую структуру данных
private static void testFindFirstSkipCount() {
Collection collection = Arrays.asList("a1", "a2", "a3", "a1");
System.out.println("Test findFirst and skip start");
// вернуть первый элемент коллекции
String first = collection.stream().findFirst().orElse("1");
System.out.println("first = " + first); // напечатает first = a1
// вернуть последний элемент коллекции
String last = collection.stream().skip(collection.size() - 1).findAny().orElse("1");
System.out.println("last = " + last ); // напечатает last = a1
// найти элемент в коллекции
String find = collection.stream().filter("a3"::equals).findFirst().get();
System.out.println("find = " + find); // напечатает find = a3
// вернуть третий элемент коллекции по порядку
String third = collection.stream().skip(2).findFirst().get();
System.out.println("third = " + third); // напечатает third = a3
System.out.println();
System.out.println("Test collect start");
// выбрать все элементы по шаблону
List select = collection.stream().filter((s) -> s.contains("1")).collect(Collectors.toList());
System.out.println("select = " + select); // напечатает select = [a1, a1]
}
// Метод Limit позволяет ограничить выборку определенным количеством первых элементов
private static void testLimit() {
System.out.println();
System.out.println("Test limit start");
Collection collection = Arrays.asList("a1", "a2", "a3", "a1");
// Вернуть первые два элемента
List limit = collection.stream().limit(2).collect(Collectors.toList());
System.out.println("limit = " + limit); // напечатает limit = [a1, a2]
// Вернуть два элемента начиная со второго
List fromTo = collection.stream().skip(1).limit(2).collect(Collectors.toList());
System.out.println("fromTo = " + fromTo); // напечатает fromTo = [a2, a3]
// вернуть последний элемент коллекции
String last = collection.stream().skip(collection.size() - 1).findAny().orElse("1");
System.out.println("last = " + last ); // напечатает last = a1
}
private enum Sex {
MAN,
WOMEN
}
private static class People {
private final String name;
private final Integer age;
private final Sex sex;
public People(String name, Integer age, Sex sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
public Sex getSex() {
return sex;
}
@Override
public String toString() {
return "{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
3.2 Примеры использования distinct
Метод distinct возвращает stream без дубликатов, при этом для упорядоченного стрима (например, коллекция на основе list) порядок стабилен, для неупорядоченного — порядок не гарантируется. Рассмотрим результаты работы над коллекцией Collection ordered = Arrays.asList («a1», «a2», «a2», «a3», «a1», «a2», «a2») и Collection nonOrdered = new HashSet (ordered).
Задача | Код примера | Результат |
---|---|---|
Получение коллекции без дубликатов из неупорядоченного стрима | nonOrdered.stream ().distinct ().collect (Collectors.toList ()) | [a1, a2, a3] — порядок не гарантируется |
Получение коллекции без дубликатов из упорядоченного стрима | ordered.stream ().distinct ().collect (Collectors.toList ()); | [a1, a2, a3] — порядок гарантируется |
// Метод distinct возвращает stream без дубликатов, при этом для упорядоченного стрима (например, коллекция на основе list) порядок стабилен , для неупорядоченного - порядок не гарантируется
// Метод collect преобразует stream в коллекцию или другую структуру данных
private static void testDistinct() {
System.out.println();
System.out.println("Test distinct start");
Collection ordered = Arrays.asList("a1", "a2", "a2", "a3", "a1", "a2", "a2");
Collection nonOrdered = new HashSet<>(ordered);
// Получение коллекции без дубликатов
List distinct = nonOrdered.stream().distinct().collect(Collectors.toList());
System.out.println("distinct = " + distinct); // напечатает distinct = [a1, a2, a3] - порядок не гарантируется
List distinctOrdered = ordered.stream().distinct().collect(Collectors.toList());
System.out.println("distinctOrdered = " + distinctOrdered); // напечатает distinct = [a1, a2, a3] - порядок гарантируется
}
3.3 Примеры использования Match функций (anyMatch, allMatch, noneMatch)
Условие: дана коллекция строк Arrays.asList («a1», «a2», «a3», «a1»), давайте посмотрим, как её можно обрабатывать используя Match функции
Задача | Код примера | Результат |
---|---|---|
Найти существуют ли хоть один «a1» элемент в коллекции | collection.stream ().anyMatch («a1»:: equals) | true |
Найти существуют ли хоть один «a8» элемент в коллекции | collection.stream ().anyMatch («a8»:: equals) | false |
Найти есть ли символ »1» у всех элементов коллекции | collection.stream ().allMatch ((s) → s.contains (»1»)) | false |
Проверить что не существуют ни одного «a7» элемента в коллекции | collection.stream ().noneMatch («a7»:: equals) | true |
// Метод anyMatch - возвращает true, если условие выполняется хотя бы для одного элемента
// Метод noneMatch - возвращает true, если условие не выполняется ни для одного элемента
// Метод allMatch - возвращает true, если условие выполняется для всех элементов
private static void testMatch() {
System.out.println();
System.out.println("Test anyMatch, allMatch, noneMatch start");
Collection collection = Arrays.asList("a1", "a2", "a3", "a1");
// найти существуют ли хоть одно совпадение с шаблоном в коллекции
boolean isAnyOneTrue = collection.stream().anyMatch("a1"::equals);
System.out.println("anyOneTrue " + isAnyOneTrue); // напечатает true
boolean isAnyOneFalse = collection.stream().anyMatch("a8"::equals);
System.out.println("anyOneFlase " + isAnyOneFalse); // напечатает false
// найти существуют ли все совпадения с шаблоном в коллекции
boolean isAll = collection.stream().allMatch((s) -> s.contains("1"));
System.out.println("isAll " + isAll); // напечатает false
// сравнение на неравенство
boolean isNotEquals = collection.stream().noneMatch("a7"::equals);
System.out.println("isNotEquals " + isNotEquals); // напечатает true
}
3.4 Примеры использования Map функций (map, mapToInt, FlatMap, FlatMapToInt)
Условие: даны две коллекции collection1 = Arrays.asList («a1», «a2», «a3», «a1») и collection2 = Arrays.asList (»1,2,0»,»4,5»), давайте посмотрим как её можно обрабатывать используя различные map функции
Задача | Код примера | Результат |
---|---|---|
Добавить »_1» к каждому элементу первой коллекции | collection1.stream ().map ((s) → s + »_1»).collect (Collectors.toList ()) | [a1_1, a2_1, a3_1, a1_1] |
В первой коллекции убрать первый символ и вернуть массив чисел (int[]) | collection.stream ().mapToInt ((s) → Integer.parseInt (s.substring (1))).toArray () | [1, 2, 3, 1] |
Из второй коллекции получить все числа, перечисленные через запятую из всех элементов | collection.stream ().flatMap ((p) → Arrays.asList (p.split (»,»)).stream ()).toArray (String[]:: new) | [1, 2, 0, 4, 5] |
Из второй коллекции получить сумму всех чисел, перечисленных через запятую | collection.stream ().flatMapToInt ((p) → Arrays.asList (p.split (»,»)).stream ().mapToInt (Integer: parseInt)).sum () | 12 |
Обратите внимание: все map функции могут вернуть объект другого типа (класса), то есть map может работать со стримом строк, а на выходе дать Stream из значений Integer или получать класс людей People, а возвращать класс Office, где эти люди работают и т.п., flatMap (flatMapToInt и т.п.) на выходе должны возвращать стрим с одним, несколькими или ни одним элементов для каждого элемента входящего стрима (см. последние два примера).
// Метод Map изменяет выборку по определенному правилу, возвращает stream с новой выборкой
private static void testMap() {
System.out.println();
System.out.println("Test map start");
Collection collection = Arrays.asList("a1", "a2", "a3", "a1");
// Изменение всех элементов коллекции
List transform = collection.stream().map((s) -> s + "_1").collect(Collectors.toList());
System.out.println("transform = " + transform); // напечатает transform = [a1_1, a2_1, a3_1, a1_1]
// убрать первый символ и вернуть числа
List number = collection.stream().map((s) -> Integer.parseInt(s.substring(1))).collect(Collectors.toList());
System.out.println("number = " + number); // напечатает transform = [1, 2, 3, 1]
}
// Метод MapToInt - изменяет выборку по определенному правилу, возвращает stream с новой числовой выборкой
private static void testMapToInt() {
System.out.println();
System.out.println("Test mapToInt start");
Collection collection = Arrays.asList("a1", "a2", "a3", "a1");
// убрать первый символ и вернуть числа
int[] number = collection.stream().mapToInt((s) -> Integer.parseInt(s.substring(1))).toArray();
System.out.println("number = " + Arrays.toString(number)); // напечатает number = [1, 2, 3, 1]
}
// Метод FlatMap - похоже на Map - только вместо одного значения, он возвращает целый stream значений
private static void testFlatMap() {
System.out.println();
System.out.println("Test flat map start");
Collection collection = Arrays.asList("1,2,0", "4,5");
// получить все числовые значения, которые хранятся через запятую в collection
String[] number = collection.stream().flatMap((p) -> Arrays.asList(p.split(",")).stream()).toArray(String[]::new);
System.out.println("number = " + Arrays.toString(number)); // напечатает number = [1, 2, 0, 4, 5]
}
// Метод FlatMapToInt - похоже на MapToInt - только вместо одного значения, он возвращает целый stream значений
private static void testFlatMapToInt() {
System.out.println();
System.out.println("Test flat map start");
Collection collection = Arrays.asList("1,2,0", "4,5");
// получить сумму всех числовые значения, которые хранятся через запятую в collection
int sum = collection.stream().flatMapToInt((p) -> Arrays.asList(p.split(",")).stream().mapToInt(Integer::parseInt)).sum();
System.out.println("sum = " + sum); // напечатает sum = 12
}
3.5 Примеры использования Sorted функции
Условие: даны две коллекции коллекция строк Arrays.asList («a1», «a4», «a3», «a2», «a1», «a4») и коллекция людей класса People (с полями name — имя, age — возраст, sex — пол), вида Arrays.asList (new People («Вася», 16, Sex.MAN), new People («Петя», 23, Sex.MAN), new People («Елена», 42, Sex.WOMEN), new People («Иван Иванович», 69, Sex.MAN)). Давайте посмотрим примеры как их можно сортировать:
Задача | Код примера | Результат |
---|---|---|
Отсортировать коллекцию строк по алфавиту | collection.stream ().sorted ().collect (Collectors.toList ()) | [a1, a1, a2, a3, a4, a4] |
Отсортировать коллекцию строк по алфавиту в обратном порядке | collection.stream ().sorted ((o1, o2) → -o1.compareTo (o2)).collect (Collectors.toList ()) | [a4, a4, a3, a2, a1, a1] |
Отсортировать коллекцию строк по алфавиту и убрать дубликаты | collection.stream ().sorted ().distinct ().collect (Collectors.toList ()) | [a1, a2, a3, a4] |
Отсортировать коллекцию строк по алфавиту в обратном порядке и убрать дубликаты | collection.stream ().sorted ((o1, o2) → -o1.compareTo (o2)).distinct ().collect (Collectors.toList ()) | [a4, a3, a2, a1] |
Отсортировать коллекцию людей по имени в обратном алфавитном порядке | peoples.stream ().sorted ((o1, o2) → -o1.getName ().compareTo (o2.getName ())).collect (Collectors.toList ()) | [{'Петя'}, {'Иван Иванович'}, {'Елена'}, {'Вася'}] |
Отсортировать коллекцию людей сначала по полу, а потом по возрасту | peoples.stream ().sorted ((o1, o2) → o1.getSex () != o2.getSex ()? o1.getSex (). compareTo (o2.getSex ()): o1.getAge ().compareTo (o2.getAge ())).collect (Collectors.toList ()) |
[{'Вася'}, {'Петя'}, {'Иван Иванович'}, {'Елена'}] |
// Метод Sorted позволяет сортировать значения либо в натуральном порядке, либо задавая Comparator
private static void testSorted() {
System.out.println();
System.out.println("Test sorted start");
// ************ Работа со строками
Collection collection = Arrays.asList("a1", "a4", "a3", "a2", "a1", "a4");
// отсортировать значения по алфавиту
List sorted = collection.stream().sorted().collect(Collectors.toList());
System.out.println("sorted = " + sorted); // напечатает sorted = [a1, a1, a2, a3, a4, a4]
// отсортировать значения по алфавиту и убрать дубликаты
List sortedDistinct = collection.stream().sorted().distinct().collect(Collectors.toList());
System.out.println("sortedDistinct = " + sortedDistinct); // напечатает sortedDistinct = [a1, a2, a3, a4]
// отсортировать значения по алфавиту в обратном порядке
List sortedReverse = collection.stream().sorted((o1, o2) -> -o1.compareTo(o2)).collect(Collectors.toList());
System.out.println("sortedReverse = " + sortedReverse); // напечатает sortedReverse = [a4, a4, a3, a2, a1, a1]
// отсортировать значения по алфавиту в обратном порядке и убрать дубликаты
List distinctReverse = collection.stream().sorted((o1, o2) -> -o1.compareTo(o2)).distinct().collect(Collectors.toList());
System.out.println("distinctReverse = " + distinctReverse); // напечатает sortedReverse = [a4, a3, a2, a1]
// ************ Работа с объектами
// Зададим коллекцию людей
Collection peoples = Arrays.asList(
new People("Вася", 16, Sex.MAN),
new People("Петя", 23, Sex.MAN),
new People("Елена", 42, Sex.WOMEN),
new People("Иван Иванович", 69, Sex.MAN)
);
// Отсортировать по имени в обратном алфавитном порядке
Collection byName = peoples.stream().sorted((o1,o2) -> -o1.getName().compareTo(o2.getName())).collect(Collectors.toList());
System.out.println("byName = " + byName); // byName = [{name='Петя', age=23, sex=MAN}, {name='Иван Иванович', age=69, sex=MAN}, {name='Елена', age=42, sex=WOMEN}, {name='Вася', age=16, sex=MAN}]
// Отсортировать сначала по полу, а потом по возрасту
Collection bySexAndAge = peoples.stream().sorted((o1, o2) -> o1.getSex() != o2.getSex() ? o1.getSex().
compareTo(o2.getSex()) : o1.getAge().compareTo(o2.getAge())).collect(Collectors.toList());
System.out.println("bySexAndAge = " + bySexAndAge); // bySexAndAge = [{name='Вася', age=16, sex=MAN}, {name='Петя', age=23, sex=MAN}, {name='Иван Иванович', age=69, sex=MAN}, {name='Елена', age=42, sex=WOMEN}]
}
private enum Sex {
MAN,
WOMEN
}
private static class People {
private final String name;
private final Integer age;
private final Sex sex;
public People(String name, Integer age, Sex sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
public Sex getSex() {
return sex;
}
@Override
public String toString() {
return "{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof People)) return false;
People people = (People) o;
return Objects.equals(name, people.name) &&
Objects.equals(age, people.age) &&
Objects.equals(sex, people.sex);
}
@Override
public int hashCode() {
return Objects.hash(name, age, sex);
}
}
3.6 Примеры использования Max и Min функций
Условие: дана коллекция строк Arrays.asList («a1», «a2», «a3», «a1»), и коллекция класса Peoples из прошлых примеров про Sorted и Filter функции.
Задача | Код примера | Результат |
---|---|---|
Найти максимальное значение среди коллекции строк | collection.stream ().max (String: compareTo).get () | a3 |
Найти минимальное значение среди коллекции строк | collection.stream ().min (String: compareTo).get () | a1 |
Найдем человека с максимальным возрастом | peoples.stream ().max ((p1, p2) → p1.getAge ().compareTo (p2.getAge ())).get () | {name='Иван Иванович', age=69, sex=MAN} |
Найдем человека с минимальным возрастом | peoples.stream ().min ((p1, p2) → p1.getAge ().compareTo (p2.getAge ())).get () | {name='Вася', age=16, sex=MAN} |
// Метод max вернет максимальный элемент, в качестве условия использует компаратор
// Метод min вернет минимальный элемент, в качестве условия использует компаратор
private static void testMinMax() {
System.out.println();
System.out.println("Test min and max start");
// ************ Работа со строками
Collection collection = Arrays.asList("a1", "a2", "a3", "a1");
// найти максимальное значение
String max = collection.stream().max(String::compareTo).get();
System.out.println("max " + max); // напечатает a3
// найти минимальное значение
String min = collection.stream().min(String::compareTo).get();
System.out.println("min " + min); // напечатает a1
// ************ Работа со сложными объектами
// Зададим коллекцию людей
Collection peoples = Arrays.asList(
new People("Вася", 16, Sex.MAN),
new People("Петя", 23, Sex.MAN),
new People("Елена", 42, Sex.WOMEN),
new People("Иван Иванович", 69, Sex.MAN)
);
// найти человека с максимальным возрастом
People older = peoples.stream().max((p1, p2) -> p1.getAge().compareTo(p2.getAge())).get();
System.out.println("older " + older); // напечатает {name='Иван Иванович', age=69, sex=MAN}
// найти человека с минимальным возрастом
People younger = peoples.stream().min((p1, p2) -> p1.getAge().compareTo(p2.getAge())).get();
System.out.println("younger " + younger); // напечатает {name='Вася', age=16, sex=MAN}
}
private enum Sex {
MAN,
WOMEN
}
private static class People {
private final String name;
private final Integer age;
private final Sex sex;
public People(String name, Integer age, Sex sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
public Sex getSex() {
return sex;
}
@Override
public String toString() {
return "{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof People)) return false;
People people = (People) o;
return Objects.equals(name, people.name) &&
Objects.equals(age, people.age) &&
Objects.equals(sex, people.sex);
}
@Override
public int hashCode() {
return Objects.hash(name, age, sex);
}
}
3.7 Примеры использования ForEach и Peek функций
Обе ForEach и Peek по сути делают одно и тоже, меняют свойства объектов в стриме, единственная разница между ними в том что ForEach терминальная и она заканчивает работу со стримом, в то время как Peek конвейерная и работа со стримом продолжается. Например, есть коллекция:
Collection list = Arrays.asList(new StringBuilder("a1"), new StringBuilder("a2"), new StringBuilder("a3"));
И нужно добавить к каждому элементу »_new», то для ForEach код будет
list.stream().forEachOrdered((p) -> p.append("_new")); // list - содержит [a1_new, a2_new, a3_new]
а для peek код будет
List newList = list.stream().peek((p) -> p.append("_new")).collect(Collectors.toList()); // и list и newList содержат [a1_new, a2_new, a3_new]
// Метод ForEach применяет указанный метод к каждому элементу стрима и заканчивает работу со стримом
private static void testForEach() {
System.out.println();
System.out.println("For each start");
Collection collection = Arrays.asList("a1", "a2", "a3", "a1");
// Напечатать отладочную информацию по каждому элементу стрима
System.out.print("forEach = ");
collection.stream().map(String::toUpperCase).forEach((e) -> System.out.print(e + ",")); // напечатает forEach = A1,A2,A3,A1,
System.out.println();
Collection list = Arrays.asList(new StringBuilder("a1"), new StringBuilder("a2"), new StringBuilder("a3"));
list.stream().forEachOrdered((p) -> p.append("_new"));
System.out.println("forEachOrdered = " + list); // напечатает forEachOrdered = [a1_new, a2_new, a3_new]
}
// Метод Peek возвращает тот же стрим, но при этом применяет указанный метод к каждому элементу стрима
private static void testPeek() {
System.out.println();
System.out.println("Test peek start");
Collection collection = Arrays.asList("a1", "a2", "a3", "a1");
// Напечатать отладочную информацию по каждому элементу стрима
System.out.print("peak1 = ");
List peek = collection.stream().map(String::toUpperCase).peek((e) -> System.out.print(e + ",")).
collect(Collectors.toList());
System.out.println(); // напечатает peak1 = A1,A2,A3,A1,
System.out.println("peek2 = " + peek); // напечатает peek2 = [A1, A2, A3, A1]
Collection list = Arrays.asList(new StringBuilder("a1"), new StringBuilder("a2"), new StringBuilder("a3"));
List newList = list.stream().peek((p) -> p.append("_new")).collect(Collectors.toList());
System.out.println("newList = " + newList); // напечатает newList = [a1_new, a2_new, a3_new]
}
3.8 Примеры использования Reduce функции
Метод reduce позволяет выполнять агрегатные функции на всей коллекцией (такие как сумма, нахождение минимального или максимального значение и т.п.), он возвращает одно значение для стрима, функция получает два аргумента — значение полученное на прошлых шагах и текущее значение.
Условие: Дана коллекция чисел Arrays.asList (1, 2, 3, 4, 2) выполним над ними несколько действий используя reduce.
Задача | Код примера | Результат |
---|---|---|
Получить сумму чисел или вернуть 0 | collection.stream ().reduce ((s1, s2) → s1 + s2).orElse (0) | 12 |
Вернуть максимум или -1 | collection.stream ().reduce (Integer: max).orElse (-1) | 4 |
Вернуть сумму нечетных чисел или 0 | collection.stream ().filter (o → o % 2!= 0).reduce ((s1, s2) → s1 + s2).orElse (0) | 4 |