Obiekty ArrayListy odwołujące się do tych samych pól, klasy wewnętrzne i zmienne finalne

0

Witam, nowicjusz ze mnie i dlatego w ramach nauki piszę sobie jakieś tam programy w Swingu. Aktualnie mam problem z tym kodem:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

import java.util.ArrayList;

/**
 * Created by sokar on 03.11.15.
 */
public class MouseEventsInPainLikeAPP {
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable(){
            @Override
            public void run() {
                JFrame ramka = new RamkaDoRysowania();
                ramka.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                ramka.setVisible(true);
            }
        });
    }

}

class RamkaDoRysowania extends JFrame{
    public RamkaDoRysowania(){
        this.setTitle("Rysuj");
        this.setSize(600,400);
        JPanel panel = new PanelDoRysowania();
        this.add(panel);
    }
}

class PanelDoRysowania extends JPanel{
    private JPanel panel = null;
    ArrayList<Line> lines = null;

    public PanelDoRysowania(){
        panel = this;
        lines = new ArrayList<Line>();
        //definiowanie kursora, zmiana na krzyżyk
        panel.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {
                super.mouseMoved(e);
                setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
            }
        });

        this.addChangingBackgroundButton(Color.RED, "Red");
        this.addChangingBackgroundButton(Color.GREEN, "Green");
        this.addChangingBackgroundButton(Color.BLUE, "Blue");

        this.addPaintALineButton();

    }

    private void addChangingBackgroundButton(final Color c, String colorName){
        JButton button = new JButton(colorName);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                panel.setBackground(c);
            }
        });
        //zmiana kusrosra na domyśny
        button.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {

                panel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            }
        });
        panel.add(button);
    }

    private void addPaintALineButton(){
        final Point startPoint = new Point();
        final Point stopPoint = new Point();
        JButton button = new JButton("Paint a line");
        panel.add(button);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                panel.addMouseListener(new MouseAdapter() {
                    @Override
                    public void mousePressed(MouseEvent e) {
                        startPoint.setLocation(e.getPoint());

                    }
                    @Override
                    public void mouseReleased(MouseEvent e) {
                        stopPoint.setLocation(e.getPoint());
                        Line line = new Line(startPoint, stopPoint);
                        lines.add(line);
                        System.out.println(lines.size());
                        System.out.println(startPoint.toString() + stopPoint.toString());
                        System.out.println(lines.get(0).getStartPoint().toString() + lines.get(0).getStopPoint().toString());
                        repaint();
                    }
                });
            }
        });

    }

    private static class Line{
        private Point startPoint = null;
        private Point stopPoint = null;

        public Line(Point startPoint, Point stopPoint) {
            this.startPoint = startPoint;
            this.stopPoint = stopPoint;
        }

        public Point getStartPoint(){
            return startPoint;
        }

        public Point getStopPoint() {
            return stopPoint;
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        for(Line l: lines){
            g2d.drawLine(l.getStartPoint().x, l.getStartPoint().y, l.getStopPoint().x, l.getStopPoint().y);
            //System.out.println(lines.size());
            //System.out.println(l.getStartPoint().toString() + l.getStopPoint().toString());
        }
    }
}
 

Problem polega na tym, że metoda JPanel.paintComponent() rysuje przy repaint() tylko jedną linie a właściwie wiele linii znajdujących się w *ArreyList lines * tyle, że te zawsze są takie same. Błąd jest gdzieś w metodzie addPaintALineButton() polegający na tym, że wszystkie linie line należące do ArreyList List mają wewnętrzne referencje do tych samych startPoint i stopPoint.
Tyle że ja nowicjusz i nie bardzo widzę co gdzie pomotałem z tymi klasami wewnętrznymi i finalnymi zmiennymi, także jak by ktoś dobry łopatologicznie wytłumaczył gdzie popełniłem błąd i dlaczego.

Pozdrawiam

0

Wszystkie obiekty typu Line mają referencję do tych samych obiektów startPoint i stopPoint - bo tylko raz tworzysz te obiekty.
Dodaj w klasie PanelDoRysowania pole startPoint

class PanelDoRysowania extends JPanel{
    Point startPoint = null;
    private JPanel panel = null;
    ArrayList<Line> lines = null;
    ...

i zmień metodę addPaintALineButton

    private void addPaintALineButton(){
        
        
        JButton button = new JButton("Paint a line");
        panel.add(button);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                panel.addMouseListener(new MouseAdapter() {
                    @Override
                    
                    public void mousePressed(MouseEvent e) {
                        startPoint = new Point();
                        startPoint.setLocation(e.getPoint());
 
                    }
                    @Override
                    
                    public void mouseReleased(MouseEvent e) {
                        Point stopPoint = new Point();
                        stopPoint.setLocation(e.getPoint());
                        Line line = new Line(startPoint, stopPoint);
                        lines.add(line);
                        //System.out.println(lines.size());
                        //System.out.println(startPoint.toString() + stopPoint.toString());
                        //System.out.println(lines.get(0).getStartPoint().toString() + lines.get(0).getStopPoint().toString());
                        repaint();
                    }
                });
            }
        });
 
    }

Edit, kod możesz trochę skrócić, zamiast

                        startPoint = new Point();
                        startPoint.setLocation(e.getPoint());

daj

                        startPoint = new Point(e.getPoint());
0

Trochę jeszcze inaczej zrobiłem. Z racji tej, że póki co nie potrzebuję zmiennych startPoint i stopPoint nigdzie na zewnątrz to dałem ich deklaracje do anonimowej klasy wewnętrznej dziedziczącej po MauseAdapter czyli metoda addPaintALineButton() wygląda teraz tak:

private void addPaintALineButton(){

        JButton button = new JButton("Paint a line");
        panel.add(button);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                panel.addMouseListener(new MouseAdapter() {
                    Point startPoint = new Point();
                    Point stopPoint = new Point();

                    @Override
                    public void mousePressed(MouseEvent e) {
                        startPoint = e.getPoint();

                    }
                    @Override
                    public void mouseReleased(MouseEvent e) {
                        stopPoint = e.getPoint();
                        Line line = new Line(startPoint, stopPoint);
                        lines.add(line);
                        System.out.println(lines.size());
                        System.out.println(startPoint.toString() + stopPoint.toString());
                        System.out.println(lines.get(0).getStartPoint().toString() + lines.get(0).getStopPoint().toString());
                        repaint();
                    }
                });
            }
        });

    }

Związku z powyższymi problemami mam pytanie jakie ma zastosowanie (jakiś przykład) i kiedy przydaje się dostęp z lokalnych klas wewnętrznych do finalnych zmiennych metod je otaczających? Bo ograniczenie w postaci final jest dość bolesne, to już chyba lepiej dodawać pola do klasy zewnętrznej?

1 użytkowników online, w tym zalogowanych: 0, gości: 1