Set.stream(): примеры (JAVA)
Set.stream(): StreamОписание метода Set.stream()
В Java метод stream() не специфичен только для Set. Он определяется в интерфейсе Collection (появился в Java 8) и предоставляется всем реализациям коллекций, включая HashSet, TreeSet, LinkedHashSet и другие. Сигнатура:
default Streamstream()
Ключевые свойства и поведение:
- Аргументы: отсутствуют. Метод вызывается на экземпляре коллекции.
- Возвращаемое значение: последовательный
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) Параллельный сбор в потокобезопасную коллекцию
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) Плоское отображение множества множеств
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 для неизменяемого результата
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) Уникальность по ключу для объектов
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 для отладки внутри цепочки
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) Сочетание нескольких множеств в один поток и дальнейшая агрегация
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) Оптимизация: избегание лишних промежуточных структур при больших объемах
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
Пояснение: при больших данных внимание к памяти и возможности параллелизации.