java - معنى - هرماش جافا




متكررة من خلال مجموعة ، وتجنب ConcurrentModificationException عند إزالة في حلقة (14)

نعلم جميعًا أنك لا تستطيع فعل هذا:

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

ConcurrentModificationException etc ... هذا يعمل على ما يبدو في بعض الأحيان ، ولكن ليس دائما. إليك بعض الرموز المحددة:

public static void main(String[] args) {
    Collection<Integer> l = new ArrayList<Integer>();

    for (int i=0; i < 10; ++i) {
        l.add(new Integer(4));
        l.add(new Integer(5));
        l.add(new Integer(6));
    }

    for (Integer i : l) {
        if (i.intValue() == 5) {
            l.remove(i);
        }
    }

    System.out.println(l);
}

هذا ، بالطبع ، يؤدي إلى:

Exception in thread "main" java.util.ConcurrentModificationException

... على الرغم من أن العديد من المواضيع لا تفعل ذلك ... على أي حال.

ما هو الحل الأفضل لهذه المشكلة؟ كيف يمكنني إزالة عنصر من المجموعة في حلقة بدون طرح هذا الاستثناء؟

أنا أيضًا أستخدم Collection تعسفية هنا ، وليس بالضرورة ArrayList ، لذلك لا يمكنك الاعتماد get .


أنشئ نسخة من القائمة الحالية وتكرارها على النسخة الجديدة.

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}

الناس يؤكدون أنه لا يمكن لأحد أن يزيل من مجموعة يتم تكرارها بواسطة حلقة foreach. أردت فقط أن أشير إلى أن هذا غير صحيح من الناحية الفنية ويصف بالضبط (أعرف أن سؤال البروتوكول الاختياري متقدم للغاية حتى يتجنب معرفة ذلك) الرمز وراء هذا الافتراض:

    for (TouchableObj obj : untouchedSet) {  // <--- This is where ConcurrentModificationException strikes
        if (obj.isTouched()) {
            untouchedSet.remove(obj);
            touchedSt.add(obj);
            break;  // this is key to avoiding returning to the foreach
        }
    }

لا يعني ذلك أنه لا يمكنك الإزالة من Colletion المتكرر بدلاً من أنه لا يمكنك الاستمرار في التكرار بمجرد Colletion ذلك. ومن هنا break في الكود أعلاه.

اعتذارات إذا كانت هذه الإجابة حالة استخدام متخصصة إلى حد ما وأكثر ملاءمة للخيمة الأصلية التي وصلت إليها من هنا ، فستتم الإشارة إلى هذا الشخص على أنه نسخة مكررة (على الرغم من أن هذا الخيط يظهر بشكل أكثر دقة) لهذا الغرض ويتم قفله.


بالإضافة إلى answer @assylias يمكنك أيضًا استخدام واجهة برمجة تطبيقات Stream الجديدة إذا كنت تستخدم Java 8:

List<Integer> l = Arrays.asList(4, 5, 6);

static boolean condition(Integer i) {
    return i == 5;
}

static Predicate<Integer> predicate = YourClassName::condition;

l.stream()
    .filter(predicate.negate())
    .forEach(System.out::println);

إذا قمت بعكس هذه الحالة ، يكون الحل أكثر إيجازًا لأنك لا تحتاج إلى negate() المسند ، مما يسمح لك باستخدام مرجع الأسلوب فقط:

List<Integer> l = Arrays.asList(4, 5, 6);

static boolean condition(Integer i) {
    return i != 5;    // <-- condition has been negated
}

l.stream()
    .filter(YourClassName::condition)
    .forEach(System.out::println);

واحدة من جمال هذا هو أن يتم تقييم تيار الباسلة ، أي لا يتم تقييم عملية filter() بالفعل حتى يتم استخدامه من قبل عملية المحطة الطرفية مثل forEach() . المزيد عن هذا يمكن العثور عليه في Oracle's Tutorial .


بما أن السؤال قد تمت الإجابة عليه بالفعل ، فإن أفضل طريقة هي استخدام طريقة إزالة كائن المكرر ، سوف أذهب إلى تفاصيل المكان الذي يتم فيه طرح الخطأ "java.util.ConcurrentModificationException" .

يحتوي كل فئة تجميع على فئة خاصة تقوم بتنفيذ واجهة Iterator وتوفر طرق مثل next() remove() و hasNext() .

رمز التالي يبدو شيء من هذا القبيل ...

public E next() {
    checkForComodification();
    try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
    } catch(IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

هنا يتم تنفيذ عملية checkForComodification

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

لذا ، كما ترى ، إذا حاولت بشكل صريح إزالة عنصر من المجموعة. ينتج عنه modCount الحصول على مختلفة من expectedModCount modCount ، مما يؤدي إلى الاستثناء ConcurrentModificationException .


في مثل هذه الحالات ، من المفترض أن تكون الخدعة الشائعة هي العودة إلى الوراء:

for(int i = l.size() - 1; i >= 0; i --) {
  if (l.get(i) == 5) {
    l.remove(i);
  }
}

ومع ذلك ، أنا أكثر من سعيد أن لديك طرق أفضل في Java 8 ، على سبيل المثال removeIf أو filter على مجموعات البث.


قد لا يكون هذا هو أفضل طريقة ، ولكن بالنسبة لمعظم الحالات الصغيرة ، يجب أن يكون هذا مقبولًا:

"إنشاء صفيف فارغ الثاني وإضافة فقط تلك التي تريد الاحتفاظ بها"

لا أتذكر أين أقرأ هذا من ... من أجل السعادة سأجعل هذا الويكي على أمل أن يجده شخص ما أو فقط لا يكسب مندوباً لا يستحقه.


لدي اقتراح للمشكلة أعلاه. لا حاجة للقائمة الثانوية أو أي وقت إضافي. يرجى العثور على مثال من شأنه أن يفعل نفس الأشياء ولكن بطريقة مختلفة.

//"list" is ArrayList<Object>
//"state" is some boolean variable, which when set to true, Object will be removed from the list
int index = 0;
while(index < list.size()) {
    Object r = list.get(index);
    if( state ) {
        list.remove(index);
        index = 0;
        continue;
    }
    index += 1;
}

هذا من شأنه تجنب استثناء التزامن.


ما أغباني:

Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
    if (iter.next().intValue() == 5) {
        iter.remove();
    }
}

لقد افترضت أنه نظرًا لأن حلقة foreach هي عبارة عن سكر نحوي متكرر ، فإن استخدام مُكرر لن يساعد ... ولكنه يمنحك هذا .remove() .


مع التقليدية للحلقة

ArrayList<String> myArray = new ArrayList<>();

   for (int i = 0; i < myArray.size(); ) {
        String text = myArray.get(i);
        if (someCondition(text))
             myArray.remove(i);
        else 
             i++;
      }

نفس الجواب ك Claudius مع حلقة for:

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}

يمكنك إما استخدام المكرر مباشرة كما ذكرت ، أو الاحتفاظ بمجموعة ثانية وإضافة كل عنصر تريد إزالته إلى المجموعة الجديدة ، ثم إزالة الكل في النهاية. يسمح لك هذا بالاستمرار في استخدام نوع أمان حلقة for-each على حساب زيادة استخدام الذاكرة ووقت المعالج (لا يجب أن تكون مشكلة كبيرة ما لم يكن لديك بالفعل قوائم كبيرة أو كمبيوتر قديم بالفعل)

public static void main(String[] args)
{
    Collection<Integer> l = new ArrayList<Integer>();
    Collection<Integer> itemsToRemove = new ArrayList<Integer>();
    for (int i=0; i < 10; ++i) {
    l.add(new Integer(4));
    l.add(new Integer(5));
    l.add(new Integer(6));
    }
    for (Integer i : l)
    {
        if (i.intValue() == 5)
            itemsToRemove.add(i);
    }

    l.removeAll(itemsToRemove);
    System.out.println(l);
}

يمكنك تكرار القائمة باستخدام for-loop وتحتاج إلى استدعاء list.remove (0). تحتاج إلى رمز الثابت فهرس مؤشر إزالة إزالة مع الصفر. انظر أيضا هذه الإجابة :

List<Integer> list = new ArrayList<Integer>();

list.add(1);
list.add(2);
list.add(3);
list.add(4);
int list_size = list.size();
for (int i = 0; i < list_size; i++) {
    list.remove(0);
}

List<String> strings=new ArrayList<String>(){};

while(strings.size() > 0) {

 String str = strings.remove(0);
}

for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

يكون catch بعد إزالة العنصر من القائمة إذا قمت بتخطي مكالمة iterator.next () الداخلية. لا يزال يعمل! على الرغم من أنني لا أقترح كتابة التعليمات البرمجية مثل هذا يساعد على فهم المفهوم الكامن وراء ذلك :-)

في صحتك!







collections