Search code examples
javaarraysactionlisteneractionevent

Add actionPerformed with array


I am coding a Tic-Tac-Toe game.

I created the buttons via an array but now I tried to add an ActionListener and an actionPerformed to every Button via a for-loop. When I try to do that an error occurs :

Local variable i defined in an enclosing scope must be final or effectively final

I don't know how to fix that. (At line 35: System.out.println("You clicked the Button" + i + ", " + k);)

import java.awt.FlowLayout;
import java.util.Random;
import java.awt.event.*;
import javax.swing.*;

public class TicTacToearray {
    
    static String players; 
    static JLabel gametext;
    static int round;
    static int maxWidth = 3;
    static int maxHeight = 3;
    static JButton[][] buttonfields = new JButton[maxWidth][maxHeight];
    
    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
        JFrame mainFrame = new JFrame("Tic-Tac-Toe");
        mainFrame.getContentPane().setLayout(new FlowLayout());
        for (int i = 0; i < maxWidth; i++) {
            for (int k = 0; k < maxHeight; k++) {
                buttonfields [i][k] = new JButton("  ");
                mainFrame.add(buttonfields[i][k], maxWidth * i + k);
                buttonfields[i][k].addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        System.out.println("You clicked the Button" + i + ", " + k);
                        System.out.println("Does that work:" + buttonfields[i][k]);
                    }
                });
            }
        }
        mainFrame.pack();
        mainFrame.setVisible(true);
        mainFrame.setResizable(true);
        mainFrame.setSize(180,150);
        mainFrame.setResizable(false);
        JLabel gametext = new JLabel();
        mainFrame.add(gametext);
        mainFrame.setLocationRelativeTo(null);
        Random rand = new Random();
        int beginnernr = rand.nextInt(2);
        if (beginnernr == 1) {
            players = "O";
            gametext.setText("It's " + players + " turn!");
        } if (beginnernr == 0) {
            players = "X";
            gametext.setText("It's " + players + " turn!");
        }
        if (round == 9) {
            gametext.setText("Game Over");
        }
    }
}

Solution

  • The 'Anonymous' new ActionListener() {...} (it can be replaced with a lambda) you are trying to create is something it will be executed after the action on the button will be done. To be able to compile the values used inside that anonymous/lambda they need to be final to guarantee they are accessible on runtime.

    A quick fix can be something like:

    final int finalI = i;
    final int finalK = k;
    buttonfields[i][k].addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            System.out.println("You clicked the Button" + finalI + ", " + finalK);
            System.out.println("Does that work:" + buttonfields[finalI][finalK]);
        }
    });
    

    In addition, using lambdas the code can look like:

    final int finalI = i;
    final int finalK = k;
    buttonfields[i][k].addActionListener(e -> {
        System.out.println("You clicked the Button" + finalI + ", " + finalK);
        System.out.println("Does that work:" + buttonfields[finalI][finalK]);
    });
    

    A bit more compact.