Як зробити ітерацію та модифікацію наборів Java?


80

Скажімо, у мене є набір цілих чисел, і я хочу збільшити кожне ціле число в наборі. Як би я це зробив?

Чи можна мені додавати та видаляти елементи з набору під час його ітерації?

Чи потрібно мені створювати новий набір, в який я б "копіював і модифікував" елементи, поки я ітератую вихідний набір?

EDIT: Що робити, якщо елементи набору незмінні?

Відповіді:


92

Ви можете безпечно видалити з набору під час ітерації за допомогою об'єкта Iterator; спроба змінити набір за допомогою його 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);

1
Чи не просто було б простіше залишити оригінальний набір для збирача сміття та продовжувати використовувати новий набір? Або тимчасовий набір збирається, або оригінальний набір, а також просто зберігайте тимчасовий набір і не турбуйтеся про мутацію оригіналу? Однак у моєму випадку це було неможливо, оскільки оригінальний комплект був остаточним, тому я прийняв.
Buttons840

@JonathanWeatherhead Ви мали на увазі це друге слово, cannotа не can? Як у, You cannot safely removeа не You can safely remove? У цьому першому абзаці здається протиріччям.
Basil Bourque

@BasilBourque ні, я мав на увазі "можу". Метод Iterator :: remove () безпечний, як зазначено в документах. docs.oracle.com/javase/7/docs/api/java/util/Iterator.html
Джонатан

error: incompatible types: Object cannot be converted to Integer
alhelal

40

Ви можете робити те, що хочете, якщо використовуєте об’єкт-ітератор для перегляду елементів у вашому наборі. Ви можете видалити їх на ходу і це нормально. Однак видалення їх, перебуваючи в циклі 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

передбачити, серед іншого, що (наголос на моєму):

" Наприклад, загалом неприпустимо, щоб один потік модифікував Колекцію, тоді як інший потік здійснює ітерацію над нею. Загалом, за цих обставин результати ітерації не визначені. Деякі реалізації Ітератора (включаючи реалізацію всієї колекції загального призначення) реалізації, передбачені JRE), можуть обрати цей виняток, якщо така поведінка виявлена ​​"(...)

" Зауважте, що цей виняток не завжди вказує на те, що об'єкт одночасно модифікувався іншим потоком. Якщо один потік видає послідовність викликів методів, що порушує контракт об'єкта, об'єкт може видавати цей виняток. Наприклад, якщо потік модифікує колекцію безпосередньо під час ітерації над колекцією за допомогою швидкого ітератора, ітератор видасть цей виняток. "

Для більш детальної деталізації: об’єкт, який можна використовувати у циклі forEach, повинен реалізувати інтерфейс „java.lang.Iterable” ( тут javadoc ). Це створює Ітератор (за допомогою методу "Ітератор", знайденого в цьому інтерфейсі), який створюється за потреби на вимогу і міститиме внутрішнє посилання на об'єкт Iterable, з якого він був створений. Однак, коли об'єкт Iterable використовується в циклі forEach, екземпляр цього ітератора прихований для користувача (ви не можете отримати до нього жодним чином доступ).

Це в поєднанні з тим фактом, що ітератор є досить вигідним, тобто для того, щоб робити свою магію і мати послідовні відповіді на свої методи "next" і "hasNext", йому потрібно, щоб об'єкт підтримки не змінювався чимось іншим, ніж сам ітератор поки він повторюється, робить це таким чином, що він видасть виняток, як тільки виявить, що щось змінилося в об'єкті, що підтримує, під час ітерації над ним.

Java називає цю "швидкодіючу" ітерацію: тобто є деякі дії, як правило, ті, що модифікують екземпляр Iterable (тоді як ітератор ітерації над ним). Частина "відмова" поняття "швидко спрацьовувати" відноситься до здатності Ітератора виявляти, коли відбуваються такі дії "відмови". "Швидка" частина "швидкого відмови" (і, яку, на мою думку, слід назвати "найвищою швидкістю"), припиняє ітерацію через ConcurrentModificationException, як тільки вона може виявити, що дія "відмови" має трапиться.


1
Чи можете ви пояснити, чому поганий шлях не вдається?
mrgloom

@mrgloom Я додав більш детальну інформацію про, в основному, чому ConcurrentModificationException можуть бути згенеровані навіть коли є тільки один потік операційної на Iterable об'єкта
Shivan Dragon

У деяких випадках ви також отримали java.lang.UnsupportedOperationException
Aguid

4

Мені не дуже подобається семантика ітератора, будь ласка, розгляньте це як варіант. Це також безпечніше, оскільки ви публікуєте менше свого внутрішнього стану

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;

}

0

Ви можете створити змінну обгортку примітивного 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, але це виходить за рамки цієї відповіді.


0

По-перше, я вважаю, що спроби робити кілька речей одночасно - це погана практика в цілому, і я пропоную вам подумати над тим, чого ви намагаєтесь досягти.

Це слугує хорошим теоретичним питанням, і з того, що я зрозумів, CopyOnWriteArraySetреалізація java.util.Setінтерфейсу відповідає вашим досить особливим вимогам.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.