Как выполнить итерацию и изменение наборов Java?


допустим, у меня есть набор целых чисел, и я хочу увеличить каждое целое число в наборе. Как бы я это сделал?

можно ли добавлять и удалять элементы из набора во время его итерации?

Мне нужно будет создать новый набор, в который я бы "копировал и изменял" элементы, пока я повторяю исходный набор?

EDIT: что делать, если элементы набора являются неизменяемыми?

5 71

5 ответов:

вы можете безопасно удалить из набора во время итерации с объектом итератора; попытка изменить набор через его API во время итерации сломает итератор. класс Set предоставляет итератор через getIterator ().

однако целочисленные объекты неизменяемы; моя стратегия будет заключаться в том, чтобы перебирать набор и для каждого целого числа i добавлять i+1 к некоторому новому временному набору. Когда вы закончите итерацию, удалите все элементы из исходного набора и добавьте все элементы новый временный набор.

Set<Integer> s; //contains your Integers
...
Set<Integer> temp = new Set<Integer>();
for(Integer i : s)
    temp.add(i+1);
s.clear();
s.addAll(temp);

вы можете делать все, что хотите, если вы используете объект итератора для перехода по элементам в вашем наборе. Вы можете удалить их на ходу, и все в порядке. Однако удаление их в то время как в цикле for (либо "стандарт", для каждого вида) приведет вас к неприятностям:

Set<Integer> set = new TreeSet<Integer>();
    set.add(1);
    set.add(2);
    set.add(3);

    //good way:
    Iterator<Integer> iterator = set.iterator();
    while(iterator.hasNext()) {
        Integer setElement = iterator.next();
        if(setElement==2) {
            iterator.remove();
        }
    }

    //bad way:
    for(Integer setElement:set) {
        if(setElement==2) {
            //might work or might throw exception, Java calls it indefined behaviour:
            set.remove(setElement);
        } 
    }

согласно комментарию @mrgloom, вот более подробная информация о том, почему "плохой" способ, описанный выше, хорошо... плохо :

Не вдаваясь в слишком много деталей о том, как Java реализует это, на высоком уровне, мы можем скажем, что "плохой" способ плох, потому что он четко оговорен как таковой в документах Java:

https://docs.oracle.com/javase/8/docs/api/java/util/ConcurrentModificationException.html

оговорите, среди прочего, что (акцент мой):

"например, обычно не допускается, чтобы один поток измените коллекцию, пока другой поток выполняет итерацию по ней. In общие сведения, результаты итерации не определены под этими обстоятельства. Некоторые реализации итератора (включая все реализации коллекции общего назначения, предоставляемые JRE) может выбрать, чтобы бросить это исключение, если это поведение обнаружено" (...)

"обратите внимание, что это исключение не всегда означает, что объект был одновременно изменен другим потоком. Если один поток выдает последовательность вызовов методов, которая нарушает договор объекта, объект может вызвать это исключение. для например, если поток изменяет коллекцию непосредственно во время ее работы итерация по коллекции с помощью итератора fail-fast, итератора будет бросать это исключение."

чтобы перейти более подробно: объект, который может быть использован в цикле forEach необходимо реализовать "java.ленг.Iterable " интерфейс (javadoc здесь). Это производит итератор (через метод "итератор", найденный в этом интерфейсе), который создается по требованию и будет содержать внутреннюю ссылку на итерационный объект, из которого он был создан. Однако, когда итерационный объект используется в цикле forEach, экземпляр этого итератора скрыт для пользователя (вы не можете получить к нему доступ самостоятельно).

это, в сочетании с тем, что итератор довольно статичен, т. е. для того, чтобы сделать свою магию и иметь когерентные ответы для своих методов "next" и "hasNext", ему нужно, чтобы объект поддержки не был измененный чем-то другим, чем сам итератор во время итерации, делает его так, что он будет выдавать исключение, как только он обнаружит, что что-то изменилось в объекте поддержки во время итерации по нему.

Java вызывает эту итерацию "fail-fast": т. е. есть некоторые действия, обычно те, которые изменяют итерационный экземпляр (в то время как итератор повторяет его). Этот "провал" часть "не-быстрый" понятие означает способность iterator, чтобы определить, когда такие "не" действия происходят. "Быстрая" часть "fail-fast" (и, на мой взгляд, должна называться "best-effort-fast") завершит итерацию через ConcurrentModificationException как только как можно обнаружить что произошло действие "сбой".

мне не очень нравится семантика итератора, пожалуйста, рассмотрите это как вариант. Это также безопаснее, как вы публикуете меньше вашего внутреннего состояния

private Map<String, String> JSONtoMAP(String jsonString) {

    JSONObject json = new JSONObject(jsonString);
    Map<String, String> outMap = new HashMap<String, String>();

    for (String curKey : (Set<String>) json.keySet()) {
        outMap.put(curKey, json.getString(curKey));
    }

    return outMap;

}

вы можете создать изменяемую оболочку примитива int и создать набор из них:

class MutableInteger
{
    private int value;
    public int getValue()
    {
        return value;
    }
    public void setValue(int value)
    {
        this.value = value;
    }
}

class Test
{
    public static void main(String[] args)
    {
        Set<MutableInteger> mySet = new HashSet<MutableInteger>();
        // populate the set
        // ....

        for (MutableInteger integer: mySet)
        {
            integer.setValue(integer.getValue() + 1);
        }
    }
}

конечно, если вы используете HashSet, вы должны реализовать метод hash, equals в своем MutableInteger, но это выходит за рамки этого ответа.

это служит хорошим теоретическим вопросом, хотя и из того, что я собираю CopyOnWriteArraySet реализация java.util.Set интерфейс удовлетворяет вашим довольно специальным требованиям.

http://download.oracle.com/javase/1,5.0/docs/api/java/util/concurrent/CopyOnWriteArraySet.html