Set.stream(): примеры (JAVA)

Работа метода stream() в коллекции Set
Раздел: Коллекции, Set
Set.stream(): Stream

Описание метода Set.stream()

В Java метод stream() не специфичен только для Set. Он определяется в интерфейсе Collection (появился в Java 8) и предоставляется всем реализациям коллекций, включая HashSet, TreeSet, LinkedHashSet и другие. Сигнатура:

default Stream stream()

Ключевые свойства и поведение:

  • Аргументы: отсутствуют. Метод вызывается на экземпляре коллекции.
  • Возвращаемое значение: последовательный java.util.stream.Stream<E> элементов коллекции.
  • Поток ленивый: промежуточные операции не выполняются до терминальной операции.
  • Не гарантирует упорядоченность элементов, если реализация Set не обеспечивает порядка. TreeSet и LinkedHashSet имеют гарантии порядка, HashSet - нет.
  • Параллельная обработка: можно получить параллельный поток через collection.parallelStream() или вызвать stream().parallel().
  • Побочные эффекты: при модификации коллекции во время обработки возможны ConcurrentModificationException или некорректные результаты.
  • Null ссылки: если переменная типа Set равна null, вызов stream() приведет к NullPointerException. Элементы коллекции могут быть null в зависимостях от реализации и последующих операций их обработка должна учитывать возможные null.

Примеры терминальных операций, возвращаемых из потока: collect, count, findFirst, forEach, toArray и т. д. Тип возвращаемого значения зависит от вызываемой терминальной операции: например, collect(Collectors.toSet()) вернёт Set, count() вернёт long, findFirst() вернёт Optional<E>.

Примечание: метод stream() сам по себе не меняет коллекцию и не даёт гарантий о неизменности результата при одновременных модификациях извне.

Короткие примеры использования

Примеры демонстрируют различные алгоритмы и типичные результаты.

1) Преобразование элементов и сбор в список

Set set = new HashSet<>(Arrays.asList("a", "b", "c"));
List list = set.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());
System.out.println(list);
[A, B, C]  // порядок может отличаться для HashSet

2) Фильтрация и сбор обратно в Set

Set nums = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Set even = nums.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toSet());
System.out.println(even);
[2, 4]  // порядок не гарантируется

3) Подсчет элементов

long count = nums.stream().count();
System.out.println(count);
5

4) Работа с упорядоченным множеством

Set tree = new TreeSet<>(Arrays.asList("b","a","c"));
String first = tree.stream().findFirst().orElse("empty");
System.out.println(first);
a  // TreeSet гарантирует сортировку

5) Параллельный поток

long sum = nums.parallelStream()
    .mapToInt(Integer::intValue)
    .sum();
System.out.println(sum);
15

Похожие методы в Java

  • parallelStream() - возвращает параллельный поток. Предпочтительнее для больших объемов данных и при поточно-безопасных операциях.
  • Arrays.stream() - создание потока из массива. Удобно, если исходные данные в массиве.
  • Stream.of(...) - создание потока из явно перечисленных значений.
  • StreamSupport.stream(Spliterator, boolean) - создание потока из Spliterator или из пользовательского Iterable.
  • Collection.forEach() - если требуется только итерация без использования функционального API, иногда быстрее и проще.

Выбор зависит от задачи: для потоковых операций и цепочек преобразований предпочтителен stream(). Для простого перебора без промежуточных преобразований достаточно forEach. Для параллельной обработки - parallelStream().

Аналоги в других языках

Краткие примеры и различия.

JavaScript (массивы)

const set = new Set(['a','b','c']);
const arr = Array.from(set).map(s => s.toUpperCase());
console.log(arr);
[ 'A', 'B', 'C' ]

Python (set и генераторы)

s = {'a','b','c'}
res = {x.upper() for x in s}
print(res)
{'A', 'B', 'C'}

C# (LINQ)

var set = new HashSet<string>{"a","b","c"};
var res = set.Select(s => s.ToUpper()).ToList();
Console.WriteLine(string.Join(",", res));
A,B,C

PHP (iterators и массивы)

$set = ['a','b','c'];
$res = array_map('strtoupper', $set);
print_r($res);
Array ( [0] => A [1] => B [2] => C )

Go (map не встроен, через слайсы)

set := []string{"a","b","c"}
res := make([]string, 0, len(set))
for _, v := range set {
    res = append(res, strings.ToUpper(v))
}
fmt.Println(res)
[A B C]

Kotlin (Sequence)

val set = setOf("a","b","c")
val res = set.asSequence().map(String::toUpperCase).toList()
println(res)
[A, B, C]

Lua (через таблицы)

local set = {"a","b","c"}
local res = {}
for i,v in ipairs(set) do res[i] = string.upper(v) end
for _,v in ipairs(res) do print(v) end
A
B
C

SQL: концепция потоков не применяется в том же виде, но преобразования выполняются запросами SELECT и агрегатами.

Отличия: в Java Stream API мощный набор промежуточных и терминальных операций, ленивость и возможность параллелизации. В других языках часто используются преобразования коллекций, генераторы или LINQ-подобные средства, но семантика параллельности и контроль ленивости различаются.

Типичные ошибки при использовании

  • Повторное использование потока: поток разовый. Повторные операции на том же объекте Stream приводят к IllegalStateException.
  • Модификация коллекции во время стрима: возможен ConcurrentModificationException или некорректный результат.
  • Ожидание порядка в HashSet: у HashSet порядок не гарантируется, поэтому сортировка или использование forEachOrdered при параллельной обработке может не дать ожидаемого результата.
  • NullPointerException при вызове stream() на null ссылке.
  • Параллелизация без учета потокобезопасности: операции с изменением общего состояния могут приводить к гонкам и неверным данным.

Примеры:

1) Повторное использование потока

Stream<String> s = Stream.of("a","b");
s.count();
s.findFirst();
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

2) Модификация коллекции внутри стрима

Set<Integer> set = new HashSet<>(Arrays.asList(1,2,3));
set.stream().forEach(i -> set.remove(i));
System.out.println(set);
Exception in thread "main" java.util.ConcurrentModificationException

3) Null ссылка

Set<String> set = null;
set.stream();
Exception in thread "main" java.lang.NullPointerException

Изменения и история

  • Метод stream() появился в Java 8 как часть Streams API.
  • Появление parallelStream() также в Java 8 для удобства параллельных вычислений.
  • В последующих версиях улучшения касались самого Streams API: Java 9 добавила Stream.ofNullable, takeWhile, dropWhile; Java 12 добавила Collectors.teeing; Java 10 ввела Collectors.toUnmodifiableSet(); Java 16 добавила Stream.toList(). Метод Collection.stream() как таковой не претерпел значительных изменений после Java 8.

Расширенные и редкие примеры использования

Несколько более сложных сценариев с пояснениями.

1) Параллельный сбор в потокобезопасную коллекцию

Пример java
Set<Integer> set = IntStream.rangeClosed(1, 1000).boxed().collect(Collectors.toSet());
Set<Integer> concurrent = set.parallelStream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toCollection(ConcurrentSkipListSet::new));
System.out.println(concurrent.size());
500

Пояснение: при параллельном сборе рекомендуется использовать потокобезопасную коллекцию или соответствующий коллектор.

2) Плоское отображение множества множеств

Пример java
Set<Set<String>> setOfSets = new HashSet<>();
setOfSets.add(new HashSet<>(Arrays.asList("a","b")));
setOfSets.add(new HashSet<>(Arrays.asList("b","c")));
Set<String> flat = setOfSets.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toSet());
System.out.println(flat);
[a, b, c]  // объединение всех подмножеств

3) Collectors.collectingAndThen для неизменяемого результата

Пример java
Set<String> set = new HashSet<>(Arrays.asList("x","y"));
Set<String> unmodifiable = set.stream()
    .collect(Collectors.collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet));
System.out.println(unmodifiable.getClass().getName());
java.util.Collections$UnmodifiableSet

4) Уникальность по ключу для объектов

Пример java
class Person { String name; int age; /* конструктор и геттеры */ }
Set<Person> people = ...;
Map<String, Person> byName = people.stream()
    .collect(Collectors.toMap(Person::getName, Function.identity(), (p1,p2) -> p1));
System.out.println(byName.size());
(число уникальных имен)

5) Использование peek для отладки внутри цепочки

Пример java
Set<Integer> set = new HashSet<>(Arrays.asList(1,2,3,4));
List<Integer> result = set.stream()
    .peek(i -> System.out.println("before filter: " + i))
    .filter(i -> i % 2 == 0)
    .peek(i -> System.out.println("after filter: " + i))
    .collect(Collectors.toList());
System.out.println(result);
before filter: 1
before filter: 2
before filter: 3
before filter: 4
after filter: 2
after filter: 4
[2, 4]

6) Сочетание нескольких множеств в один поток и дальнейшая агрегация

Пример java
Set<String> a = new HashSet<>(Arrays.asList("a","b"));
Set<String> b = new HashSet<>(Arrays.asList("b","c"));
Set<String> union = Stream.of(a, b)
    .flatMap(Collection::stream)
    .collect(Collectors.toSet());
System.out.println(union);
[a, b, c]

7) Оптимизация: избегание лишних промежуточных структур при больших объемах

Пример java
Set<Integer> large = IntStream.rangeClosed(1, 1_000_000).boxed().collect(Collectors.toSet());
long sum = large.stream()
    .mapToLong(Integer::longValue)
    .sum();
System.out.println(sum);
500000500000

Пояснение: при больших данных внимание к памяти и возможности параллелизации.

джава Set.stream() function comments

En
Set.stream() Возвращает последовательный поток