1
votes

Suppression d'un objet d'une collection lors de son itération

J'essaye de supprimer les objets qui sont en dehors de JPanel. Cependant, lorsque je fais cela, j'obtiens cette erreur

 entrez la description de l'image ici

et mon programme plante. Mon conférencier m'a dit que c'était parce que deux Threads accédaient à la ArrayList qui stockait mes objets.

J'ai fait pour synchroniser les fonctions mais ce n'est pas le cas ne fonctionne pas.

Bullet

import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;

public class Bullet {
    //Environment
    public static ArrayList<Bullet> bullet = new ArrayList<>();
    private String path = Application.path;
    private GamePanel gp;
    //properties
    private int x,y;
    private int width,height;
    private int yVector;
    private Image image;


    Bullet(GamePanel gp, int x, int y){
        image = new ImageIcon(path+"\\images\\javaicon.png").getImage();
        width=image.getWidth(null);
        height=image.getHeight(null);
        this.gp=gp;
        this.x=x;
        this.y=y;
        yVector=5;
    }

    public void move(){
        if(y< -height){
           bullet.remove(this);
        }
        y-=5;
    }

    public Image getImg(){
        return image;
    }

    public int getX(){
        return x;
    }

    public int getY(){
        return y;
    }
}

Classes pertinentes:

Application

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.security.Key;
import java.util.ArrayList;

public class GamePanel extends JPanel implements Runnable, KeyListener{
    private String path = Application.path;
    Image gameOverImg = new ImageIcon(path+"//images//gameover1.png").getImage();
    private Ihsan ihsan;
    private ArrayList <David> david = new ArrayList<>();
    private int enemies=5;
    private boolean pause=false;
    private boolean gameOver=false;

    GamePanel(){
        ihsan = new Ihsan(this);
        for(int i=0; i<enemies; i++){
            david.add(new David(this));
        }
        setFocusable(true);
        requestFocusInWindow();
        addKeyListener(this);
    }

    @Override
    public void run() {
        while (!pause){
            repaint();
            for(David david:david){
                david.move();
            }
            for(Bullet bullet:Bullet.bullet){
                bullet.move();
            }
            try{Thread.sleep(30);}
            catch (InterruptedException e){}
        }
    }

    public void paint(Graphics g){
        Graphics2D g2d = (Graphics2D) g.create();
        g2d.setColor(Color.GRAY);
        g2d.fillRect(0,0 ,getWidth(), getHeight());

        for(David david : david){
            g2d.drawImage(david.getImg(), david.getX(), david.getY(), null);
        }
        g2d.drawImage(ihsan.getImg(), ihsan.getX(), ihsan.getY(), null);
        for (Bullet bullet:Bullet.bullet){
            g2d.drawImage(bullet.getImg(), bullet.getX(), bullet.getY(), null);
        }

        if(gameOver){
            g2d.drawImage(gameOverImg,0,getHeight()/4,null);
        }
    }

    private static final Dimension DESIRED_SIZE = new Dimension(600,700);
    @Override
    public Dimension getPreferredSize(){
        return DESIRED_SIZE;
    }

    public void setGameOver(boolean gameOver) {
        this.gameOver = gameOver;
    }

    @Override
    public void keyPressed(KeyEvent e) {
        int key=e.getKeyCode();

        if (key==KeyEvent.VK_D || key==KeyEvent.VK_RIGHT){
            ihsan.move(4,0);
            System.out.println("Right Key");
        }

        if (key==KeyEvent.VK_A || key== KeyEvent.VK_LEFT){
            ihsan.move(-4,0);
            System.out.println("Left Key");
        }

        if(key==KeyEvent.VK_SPACE){
            Bullet.bullet.add(new Bullet(this,ihsan.getX()+(ihsan.getWidth()/2), ihsan.getY()));
        }
    }

    @Override
    public void keyTyped(KeyEvent e) { }

    @Override
    public void keyReleased(KeyEvent e) { }

    public boolean getGameOver(){
        return gameOver;
    }
}

GamePanel

import javax.swing.*;

public class Application {
    public static String path ="C:\\Users\\jarek\\OneDrive\\NUIG Private\\(2) Semester 2 2019\\Next Generation Technologies II CT255\\Assignment 3\\";
    private Application(){
        JFrame frame = new JFrame("Ihsan The Defender");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        GamePanel gamePanel= new GamePanel();
        frame.add(gamePanel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setVisible(true);
        new Thread(gamePanel).start();
    }

    public static void main (String args[]){
        new Application();
    }
}

Bullet strong >

public void move(){
        if(y< -height){
            synchronized (this) {
                bullet.remove(this);
            }
        }
        y-=5;
    }


0 commentaires

3 Réponses :


1
votes

Le bloc synchronisé doit être autour de chaque morceau de code qui a accédé ou modifie la ArrayList . L'objet mis entre parenthèses doit être le même: c'est le verrou.

Créez un champ de type Object nommé bulletLock par exemple, et utilisez-le comme verrou, chaque fois que vous accédez à bullet .

L'erreur se produit car vous supprimez une puce alors qu'un autre thread est dans une boucle for de la liste. Comme il y a une modification simultanée, elle ne peut pas continuer en toute sécurité.

Une autre solution serait de faire une copie de la ArrayList avant votre boucle for.


4 commentaires

Est-ce que faire une copie de ArrayList ne serait pas inefficace?


Les puces dans ArrayList seraient les mêmes instances, donc cela fonctionnerait, mais utiliserait plus de mémoire. Le mieux serait d'utiliser un bloc synchronisé.


J'ai essayé de le synchroniser et il plante toujours. Je pense que je fais quelque chose de mal. ici


Vous avez oublié de synchroniser l'ajout dans keyPressed



1
votes

Votre problème actuel ne concerne pas la synchronisation, mais que vous modifiez la liste à puces en l'itérant:

// Bullet.java#move():
    public boolean move(){
        if(y< -height){
           return true; // instruct GamePanel.run() to remove this bullet
        }
        y-=5;
        return false; // keep this bullet
    }

// GamePanel.java#run():
    Iterator<Bullet> it = Bullet.bullet.iterator();
    while (it.hasNext()) {
        Bullet bullet = it.next();
        if (bullet.move()) { // if bullet should be removed
            it.remove(); // remove it from the list
        }
    }

La synchronisation n'aidera pas, car les deux actions se produisent dans le même thread.

/ p>

Pour résoudre ce problème, la méthode Bullet.move () doit renvoyer un booléen indiquant s'il doit être supprimé de la liste. Et GamePanel.run () ne doit pas utiliser une boucle for améliorée mais un itérateur (supprimer un élément d'une liste à l'aide de Iterator.remove () est sûr si c'est le seul active Iterator):

// GamePanel.java#run():
    for (Bullet bullet:Bullet.bullet) { //your code is iterating over Bullet.bullet here
        bullet.move(); //you call Bullet#move here
    }

// Bullet.java#move():
    public void move(){
        if(y< -height){
           bullet.remove(this); //this will remove the current bullet from Bullet.bullet
           // ultimately causing the ConcurrrentModificationException in GamePanel.run()
        }
        y-=5;
    }

Il y a aussi d'autres problèmes:

  • vous appelez #repaint () à partir de votre propre thread au lieu de Swing EDT
  • repeindre itère sur la même liste Bullet.bullet sans synchronisation (ce qui peut conduire à une ConcurrentModificationException dans GamePanel.paint () )


3 commentaires

Eh bien oui, je suis conscient du deuxième problème. C'est dans la question. Je cherche une solution. Je pensais que la synchronisation réglerait le problème.


@ ertagon2 J'ai mis à jour ma réponse avec une solution possible


Merci beaucoup



1
votes

Une solution simple au problème décrit dans la réponse de Thomas Kläger pourrait être:

Iterator<Bullet> it = Bullet.bullet.iterator();
while (it.hasNext()) {
    Bullet bullet = it.next();
    bullet.move();
}


0 commentaires