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

Итерация по множествам в Java: метод iterator() и примеры
Раздел: Коллекции, Set
Set.iterator(): Iterator

Описание метода

В Java у коллекций типа Set определён метод Iterator<E> iterator(), который возвращает объект итератора для последовательного обхода элементов множества. Метод не принимает аргументов и всегда возвращает экземпляр, реализующий интерфейс java.util.Iterator. Поведение итератора зависит от конкретной реализации Set (например, HashSet, LinkedHashSet, TreeSet, а также конкурентные коллекции).

Ключевые свойства и возвращаемые значения:

  • Аргументы: отсутствуют.
  • Возвращаемое значение: Iterator<E> - объект, который предоставляет методы hasNext(), next(), remove() и с Java 8 - forEachRemaining(Consumer).
  • Порядок обхода: зависит от реализации Set. HashSet не гарантирует порядка, LinkedHashSet сохраняет порядок вставки, TreeSet возвращает отсортированный порядок.
  • Поддержка удаления: итератор большинства реализаций поддерживает remove() для удаления текущего элемента. Если удаление не поддерживается, будет выброшено UnsupportedOperationException.
  • Параллельные изменения: стандартные реализации коллекций часто реализуют fail-fast поведение - при структурном изменении коллекции вне итератора будет выброшено ConcurrentModificationException, но это поведение не гарантируется спецификацией и не относится к конкурентным коллекциям (ConcurrentHashMap.newKeySet(), CopyOnWriteArraySet и т.п.).
  • Исключения: вызов next() при отсутствии элементов приводит к NoSuchElementException; вызов remove() до вызова next() или при повторном вызове без промежуточного next() приведёт к IllegalStateException.

Типичная сигнатура в коде: Iterator<E> it = mySet.iterator();. Итератор используется для последовательного доступа, безопасного удаления текущего элемента и для передачи элементов в обработчики через forEachRemaining.

Короткие примеры

1. Простой обход HashSet:

import java.util.*;

public class Demo1 {
    public static void main(String[] args) {
        Set<String> s = new HashSet<>();
        s.add("a");
        s.add("b");
        s.add("c");

        Iterator<String> it = s.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}
Результат (порядок не гарантируется):
a
b
c

2. Удаление элемента во время обхода через итератор:

import java.util.*;

public class Demo2 {
    public static void main(String[] args) {
        Set<Integer> s = new HashSet<>(Arrays.asList(1,2,3,4));
        Iterator<Integer> it = s.iterator();
        while (it.hasNext()) {
            int v = it.next();
            if (v % 2 == 0) {
                it.remove();
            }
        }
        System.out.println(s);
    }
}
Результат:
[1, 3]

3. Ошибка при модификации коллекции вне итератора:

import java.util.*;

public class Demo3 {
    public static void main(String[] args) {
        Set<String> s = new HashSet<>(Arrays.asList("x","y","z"));
        for (String item : s) {
            s.remove(item); // изменяет коллекцию во время обхода
        }
    }
}
Результат:
Exception in thread "main" java.util.ConcurrentModificationException
    at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:...)
    ...

4. Получение итератора у отсортированного множества (TreeSet):

import java.util.*;

public class Demo4 {
    public static void main(String[] args) {
        Set<Integer> s = new TreeSet<>(Arrays.asList(3,1,2));
        for (Iterator<Integer> it = s.iterator(); it.hasNext(); ) {
            System.out.println(it.next());
        }
    }
}
Результат:
1
2
3

Альтернативы внутри Java

  • for-each (enhanced for): проще и читаемее для прохода без удаления. Пример: for (E e : set) { ... }.
  • Collection.forEach(Consumer) и Stream API: удобны для функциональной обработки, не подходят для безопасного удаления текущего элемента через итератор.
  • Spliterator: позволяет создавать последовательные и параллельные потоки, может давать порядок и оценки оставшихся элементов.
  • ListIterator: есть только для списков; предоставляет двунаправленный проход и возможность менять элементы.
  • Enumeration: устаревший подход для старых коллекций (Vector, Hashtable), имеет меньшую функциональность.

Выбор: если требуется удалить элементы при обходе, итератор предпочтителен; для простого чтения предпочитается for-each или forEach; для параллельной обработки используют Spliterator и Streams.

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

Краткие примеры и отличия:

JavaScript (ES6 Set):

const s = new Set(['a','b','c']);
const it = s.values();
console.log(it.next().value);
Результат:
a

Отличие: итераторы у Set в JS возвращают объекты с методом next() и полями {value, done}; порядок вставки сохраняется.

Python (set):

s = {'a','b','c'}
for x in s:
    print(x)
Результат (порядок не гарантируется):
a
b
c

Отличие: в Python итератор создаётся функцией iter(s); удаление элементов при обходе обычно выполняется через создание списка для удаления или через set comprehension.

C# (HashSet<T>):

var s = new HashSet<string>{"a","b","c"};
var e = s.GetEnumerator();
while (e.MoveNext()) Console.WriteLine(e.Current);
Результат:
a
b
c

Отличие: в C# перечислитель (enumerator) поддерживает MoveNext/Current, удаление через enumerator не поддерживается; для удаления используется сбор элементов для удаления.

Go (нет встроенного Set):

s := map[string]struct{}{"a":{}, "b":{}, "c":{}}
for k := range s {
    fmt.Println(k)
}
Результат (порядок не гарантируется):
a
b
c

Отличие: обход через range по карте; нет стандартного итератора-объекта.

Kotlin (Set):

val s: Set<String> = setOf("a","b","c")
val it = s.iterator()
while (it.hasNext()) println(it.next())
Результат:
a
b
c

Отличие: API близок к Java; iterator() возвращает MutableIterator для изменяемых множеств.

PHP (нет встроенного Set, используются SplObjectStorage или массивы):

$arr = ['a','b','c'];
foreach ($arr as $v) echo $v . PHP_EOL;
Результат:
a
b
c

Отличие: нет единого стандарта Set; итерация по массивам/итерируемым объектам через foreach.

Lua (таблицы):

s = {a=true, b=true, c=true}
for k,_ in pairs(s) do print(k) end
Результат (порядок не гарантируется):
a
b
c

SQL: множества представлены наборами строк; для построчной обработки служат курсоры в процедурных диалектах (PL/pgSQL, T-SQL) - другой подход, не объектный итератор.

Типичные ошибки и исключения

  • NoSuchElementException: вызов next() без проверки hasNext(). Пример:
import java.util.*;
public class Err1 { public static void main(String[] args) {
    Set<String> s = new HashSet<>();
    Iterator<String> it = s.iterator();
    it.next();
}}
Результат:
Exception in thread "main" java.util.NoSuchElementException
    at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:...)
    ...
  • IllegalStateException: вызов remove() без предварительного next() или повторный вызов remove() подряд.
import java.util.*;
public class Err2 { public static void main(String[] args) {
    Set<Integer> s = new HashSet<>(Arrays.asList(1,2));
    Iterator<Integer> it = s.iterator();
    it.remove();
}}
Результат:
Exception in thread "main" java.lang.IllegalStateException
    at java.base/java.util.HashMap$KeyIterator.remove(HashMap.java:...)
    ...
  • ConcurrentModificationException: структурные изменения коллекции вне итератора во время обхода. Пример в разделе примеров (Demo3).
  • UnsupportedOperationException: если итератор не поддерживает удаление, например у коллекций, возвращаемых Collections.unmodifiableSet(...). Пример:
Set<String> s = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a")));
Iterator<String> it = s.iterator();
it.next();
it.remove();
Результат:
Exception in thread "main" java.lang.UnsupportedOperationException
    at java.base/java.util.Collections$UnmodifiableCollection$1.remove(Collections.java:...)
    ...

Изменения и заметные дополнения

Интерфейс Iterator и метод iterator() долгое время оставались стабильными. Главные дополнения в недавних версиях Java:

  • Java 8: добавлен метод forEachRemaining(Consumer) в интерфейс Iterator, что упрощает последовательную обработку оставшихся элементов.
  • Java 8: введён Spliterator и связанный механизм создания потоков из коллекций, но iterator() остался без изменений.
  • Конкурентные коллекции (в стандартной библиотеке) предоставляют итераторы с другим поведением: вместо fail-fast некоторые дают слабосогласованный (weakly consistent) вид изменений.

Сама сигнатура Iterator<E> iterator() у Set изменений не претерпевала и остаётся совместимой назад.

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

1. Удаление по предикату с помощью итератора (без ConcurrentModificationException):

Пример java
import java.util.*;

public class Adv1 {
    public static void main(String[] args) {
        Set<Integer> nums = new HashSet<>(Arrays.asList(1,2,3,4,5,6));
        Iterator<Integer> it = nums.iterator();
        while (it.hasNext()) {
            if (it.next() % 2 == 0) it.remove();
        }
        System.out.println(nums);
    }
}
Результат:
[1, 3, 5]

2. Итератор у CopyOnWriteArraySet даёт снимок содержимого на момент создания итератора; изменения после создания не видны итератору:

Пример java
import java.util.concurrent.*;
import java.util.*;

public class Adv2 {
    public static void main(String[] args) {
        Set<String> s = new CopyOnWriteArraySet<>(Arrays.asList("a","b"));
        Iterator<String> it = s.iterator();
        s.add("c");
        while (it.hasNext()) System.out.println(it.next());
        System.out.println("После добавления: " + s);
    }
}
Результат:
a
b
После добавления: [a, b, c]

3. Слабосогласованный итератор у конкурентного набора (ConcurrentHashMap.newKeySet()): изменения могут быть видны или нет, исключений не бросается:

Пример java
import java.util.*;
import java.util.concurrent.*;

public class Adv3 {
    public static void main(String[] args) throws InterruptedException {
        Set<Integer> s = ConcurrentHashMap.newKeySet();
        for (int i=0;i<1000;i++) s.add(i);
        Iterator<Integer> it = s.iterator();
        // параллельная модификация
        new Thread(() -> { for (int i=1000;i<1100;i++) s.add(i); }).start();
        int count = 0;
        while (it.hasNext()) { it.next(); count++; }
        System.out.println("Прочитано элементов: " + count + ", текущее размер: " + s.size());
    }
}
Результат (пример):
Прочитано элементов: 1000, текущее размер: 1100

4. Обход в обратном порядке для TreeSet через descendingSet().iterator():

Пример java
import java.util.*;
public class Adv4 {
    public static void main(String[] args) {
        TreeSet<Integer> ts = new TreeSet<>(Arrays.asList(1,2,3));
        Iterator<Integer> it = ts.descendingSet().iterator();
        while (it.hasNext()) System.out.println(it.next());
    }
}
Результат:
3
2
1

5. Преобразование итератора в Stream через Spliterator:

Пример java
import java.util.*;
import java.util.stream.*;

public class Adv5 {
    public static void main(String[] args) {
        Set<String> s = new HashSet<>(Arrays.asList("a","b","c"));
        Stream<String> stream = StreamSupport.stream(s.spliterator(), false);
        System.out.println(stream.sorted().collect(Collectors.joining(",")));
    }
}
Результат:
a,b,c

6. Собственный итератор через iterator() над декорированным Set для логирования: итератор можно оборачивать, реализуя дополнительные действия при вызове методов next() и remove().

Пример java
// упрощённый пример: конструкция нового Iterator, делегирующего вызовы и логирующего
Результат: зависит от реализации - позволяет фиксировать доступы и контролировать удаление.

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

En
Set.iterator() Возвращает итератор по элементам