Перейти к основному содержимому

Метод collect

Большинство операций класса Stream, которые модифицируют набор данных, возвращают этот набор в виде потока. Однако бывают ситуации, когда хотелось бы получить данные не в виде потока, а в виде обычной коллекции, например, List или Set. И для этого у класса Stream определён метод collect. Первая версия метода принимает в качестве параметра функцию преобразования к коллекции:

<R, A> R collect(Collector<? super T, A, R> collector)

Параметр R представляет тип результата метода, параметр Т - тип элемента в потоке, а параметр А - тип промежуточных накапливаемых данных. В итоге параметр collector представляет функцию преобразования потока в коллекцию.

Эта функция представляет объект Collector, который определен в пакете java.util.stream. Мы можем написать свою реализацию функции, однако Java уже предоставляет ряд встроенных функций, определенных в классе Collectors:

  • toList(): преобразование к типу List;
  • toSet(): преобразование к типу Set;
  • toMap(): преобразование к типу Map.

Например, преобразуем набор в потоке в список:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class Program {
public static void main(String[] args) {
List<String> phones = new ArrayList<>();
Collections.addAll(phones, "iPhone 8", "HTC U12", "Huawei Nexus 6P",
"Samsung Galaxy S9", "LG G6", "Xiaomi MI6", "ASUS Zenfone 2",
"Sony Xperia Z5", "Meizu Pro 6", "Lenovo S850");

List<String> filteredPhones = phones.stream()
.filter(s -> s.length() < 10)
.collect(Collectors.toList());

for (String s : filteredPhones) {
System.out.println(s);
}
}
}

Использование метода toSet() аналогично.

Set<String> filteredPhones = phones.stream()
.filter(s -> s.length() < 10)
.collect(Collectors.toSet());

Для применения метода toMap() надо задать ключ и значение. Например, пусть у нас есть следующий класс:

class Phone {
private String name;
private int price;

public Phone(String name, int price) {
this.name = name;
this.price = price;
}

public String getName() {
return name;
}

public int getPrice() {
return price;
}
}

Теперь применим метод toMap():

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Program {
public static void main(String[] args) {
Stream<Phone> phoneStream = Stream.of(new Phone("iPhone 8", 54000),
new Phone("Nokia 9", 45000),
new Phone("Samsung Galaxy S9", 40000),
new Phone("LG G6", 32000));

Map<String, Integer> phones = phoneStream
.collect(Collectors.toMap(p -> p.getName(), t -> t.getPrice()));

phones.forEach((k, v) -> System.out.println(k + " " + v));
}
}

class Phone {
private String name;
private int price;

public Phone(String name, int price) {
this.name = name;
this.price = price;
}

public String getName() {
return name;
}

public int getPrice() {
return price;
}
}

Лямбда-выражение p -> p.getName() получает значение для ключа элемента, а t -> t.getPrice() - извлекает значение элемента.

Если нам надо создать какой-то определенный тип коллекции, например, HashSet, то мы можем использовать специальные функции, которые определены в классах-коллекций. Например, получим объект HashSet:

import java.util.HashSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Program {
public static void main(String[] args) {
Stream<String> phones = Stream.of("iPhone 8", "HTC U12", "Huawei Nexus 6P",
"Samsung Galaxy S9", "LG G6", "Xiaomi MI6", "ASUS Zenfone 2",
"Sony Xperia Z5", "Meizu Pro 6", "Lenovo S850");

HashSet<String> filteredPhones = phones.filter(s -> s.length() < 12)
.collect(Collectors.toCollection(HashSet::new));

filteredPhones.forEach(s -> System.out.println(s));
}
}

Выражение HashSet::new представляет функцию создания коллекции. Аналогичным образом можно получать другие коллекции, например, ArrayList:

ArrayList<String> result = phones.collect(Collectors.toCollection(ArrayList::new));

Вторая форма метода collect имеет три параметра:

<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
  • supplier: создает объект коллекции.
  • accumulator: добавляет элемент в коллекцию.
  • combiner: бинарная функция, которая объединяет два объекта.

Применим эту версию метода collect:

import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Program {
public static void main(String[] args) {
Stream<String> phones = Stream.of("iPhone 8", "HTC U12", "Huawei Nexus 6P",
"Samsung Galaxy S9", "LG G6", "Xiaomi MI6", "ASUS Zenfone 2",
"Sony Xperia Z5", "Meizu Pro 6", "Lenovo S850");

ArrayList<String> filteredPhones = phones.filter(s -> s.length() < 12)
.collect(
() -> new ArrayList<String>(), // создаем ArrayList
(list, item) -> list.add(item), // добавляем в список элемент
(list1, list2) -> list1.addAll(list2) // добавляем в список другой список
);

filteredPhones.forEach(s -> System.out.println(s));
}
}