It is true that you get a ConcurrentModificationException error when you try and modify an arraylist when iterating through it, yes? But how does the error still persist if you first remove the element you want to modify, iterate through the list, and then insert the element back? I do not understand if the logic is like this why it throws a concurrentmodificationexception.
This whole program uses the Observer Pattern.
The purpose of the program is to have multiple text boxes, when you type in one, the others update with the same text.
Subject - Interface
public interface Subject {
public void attach(Observer o);
public void detach(Observer o);
public void notifyAllObservers();
}
SubjectImpl - implements Subject
import java.util.*;
public class SubjectImpl implements Subject {
private List <Observer> observers;
public SubjectImpl(){
observers = new ArrayList<Observer>();
}
@Override
public void attach(Observer o) {
observers.add(o);
}
@Override
public void detach(Observer o) {
observers.remove(o);
}
@Override
public void notifyAllObservers() {
//Iterating code
for(Observer o: observers){
o.update();
}
}
}
Observer - interface
public interface Observer {
public void update();
}
Editor - Implements Observer
import java.awt.*
import java.io.*;
import javax.swing.*;
public class Editor extends JFrame implements DocumentListener, Observer {
private FileContentSubject reference;
private JScrollPane textAreaScrollPane;
private JTextArea textArea;
public Editor(FileContentSubject filecontentsubject) throws IOException {
super("Editor");
initComponents();
this.reference = filecontentsubject;
textArea.getDocument().addDocumentListener(reference);
textArea.getDocument().putProperty("ownerEditor", this);
}
private void initComponents(){
textArea = new JTextArea();
//set location, size, visible and defaultcloseoperation here
getContentPane().setLayout(new BorderLayout());
getContentPane().add(textArea, BorderLayout.CENTER);
}
@Override
public void update() {
textArea.setText(reference.getState()); //Call to update each text box
}
@Override
public void changedUpdate(DocumentEvent e) {
}
@Override
public void insertUpdate(DocumentEvent e) {
}
@Override
public void removeUpdate(DocumentEvent e) {
}
}
File Content System - Acts as concrete subject in Observer (there only exists one)
import javax.swing.event.*;
import javax.swing.text.*;
public class FileContentSubject implements Subject, DocumentListener {
private String state; //Current, most recent copy of everything
public String getState() {
return this.state;
}
private SubjectImpl reference;
@Override
public void attach(Observer o) {
reference.attach(o);
}
@Override
public void detach(Observer o) {
reference.detach(o);
}
@Override
public void notifyAllObservers() {
reference.notifyAllObservers();
}
public FileContentSubject() {
reference = new SubjectImpl();
}
@Override
public void changedUpdate(DocumentEvent arg0) {
}
@Override
public void insertUpdate(DocumentEvent arg0) {
Document doc = (Document) arg0.getDocument();
try {
this.state = doc.getText(0, doc.getLength());
} catch (BadLocationException e) {
e.printStackTrace();
}
Editor e = (Editor) doc.getProperty("ownerEditor");
reference.detach(e);
notifyAllObservers();
reference.attach(e);
}
@Override
public void removeUpdate(DocumentEvent arg0) {
//same as insertUpdate(DocumentEvent arg0) ^^
}
}
I believe your ConcurrentModificationException
is only thrown when 3 or more editors are used, right? Anyway, let me try to explain what is happening.
Let's suppose you have 3 Editor
instances: "Editor1", "Editor2" and "Editor3".
This is what happens when you type a text into "Editor1" (or, how I call it, when you "begin the chain of events"):
User types on Editor1 // chain begins
FileContentSubject detaches editor: Editor1
FileContentSubject begin notifyAllObservers(): [Editor2, Editor3]
Editor2 receives update() to new state
So far, normal behavior, right? The thing is when Editor2's update()
is called, it changes Editor2's textArea
, and FileContentSubject
is a DocumentListener
of that textArea
as well (as it was of Editor1's textArea
).
This triggers a new edition, thus creating a new "chain of events". In practice, here's what will happen (repeating from the beginning):
User types on Editor1 // chain begins
FileContentSubject detaches editor: Editor1
FileContentSubject begin notifyAllObservers(): [Editor2, Editor3] // first notification
Editor2 receives update() to new state // another chain begins! (the 1st chain didn't end yet!)
FileContentSubject detaches editor: Editor2
FileContentSubject begin notifyAllObservers(): [Editor3]
Editor3 receives update() to new state // chain THREE!!
FileContentSubject detaches editor: Editor3
FileContentSubject begin notifyAllObservers(): [] // empty list... nobody to notify
FileContentSubject reattaches editor: Editor3
FileContentSubject ended notifyAllObservers(): [Editor3] // chain 3 over
FileContentSubject reattaches editor: Editor2
FileContentSubject ended notifyAllObservers(): [Editor3, Editor2] // chain 2 over
This is the point where ConcurrentModificationException
is thrown. But where exactly is it throw, on the code?
Actually, it's on SubjectImpl
(!):
@Override
public void notifyAllObservers() {
for (Observer o : observers) {
o.update();
}
}
What is the problem?
The thing is:
// first notification
comment there's a call to notifyAllObservers()
notifyAllObservers()
, when called, then, begins iterating on that list
Editor2.update()
.
Editor2.update()
starts a whole new chain...notifyAllObservers()
tries to continue its iteration to the next step, it notices that the list has changed and... bam! ConcurrentModificationException
is thrown.Why? Because you cannot change a list where you have begun to iterate. (Either using an iterator, or "foreach".)
There may be many, but the simplest is just to stop the "chain of events". To do that, just change update()
on Editor
from:
@Override
public void update() {
textArea.setText(reference.getState()); // Call to update each text box
}
To:
@Override
public void update() {
textArea.getDocument().removeDocumentListener(reference);
textArea.setText(reference.getState()); // Call to update each text box
textArea.getDocument().addDocumentListener(reference);
}
What does it mean? It means that when an Editor
changes itself due to anybody else and not some typing on its textArea
, he won't notify FileContentSubject
(the DocumentListener
) anymore, he'll just change, quietly.
This will stop any second chain of events from beginning in the first place.
Hope I managed to explain the problem! If I can make any point clearer, just let me know! Cheers!