Search code examples
javaswingjbuttonactionlisteneractionevent

Changing value of parameter within a JButton ActionEvent returns error, "must be final or effectively final"


I am trying to make a function that creates a JButton. The button either increases or decreases an R, G, or B value(red, green, blue). The user enters a few parameters,

  • the buttons parent (JPanel)
  • The text on the button (String)
  • The function of the button either increase or decrease (boolean)
  • The color value that the user wishes to change (int)

So if the user wishes to increase green, the integer value, userGreen should increase by 15. However when I tried increasing it I got an error, saying:

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

My actual program has a lot of variables and other functions attached and attaching the whole program would be far too long, so I made the shortest example I could. This example simply counts the number of times the button is pressed and prints it to the console.

This produces the same error

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

Is it possible to change the value of a parameter within a JButton's Action Listener? And why does the variable need to be final?

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

public class ButtonTest {
    int clickNumber = 0;
    
    public ButtonTest() {
        JFrame frame = new JFrame("Button Test");
        frame.add(newButton(frame, "Click me", clickNumber));
        frame.pack();
        frame.setVisible(true);
    }

    private JButton newButton(JFrame parent, String text, int clickNumber) {
        JButton btn = new JButton(text);
        btn.setBackground(Color.WHITE);
        
        btn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                System.out.println(clickNumber); //error here, "Local variable clickNumber defined in an enclosing scope must be final or effectively final"
            }
        });
        clickNumber += 1; // if i remove this than the error goes away, so changing the value of the variable is somehow related
        
        parent.add(btn);
        return btn;
    }
    
    public static void main(String[] args) {
        try {
            String laf = UIManager.getCrossPlatformLookAndFeelClassName();
            UIManager.setLookAndFeel(laf);
        } 
        catch (Exception e) {}

        SwingUtilities.invokeLater(new Runnable(){
            public void run() {
                new ButtonTest();
            }
        });
    }
}

Solution

  • You cannot access a variable in a local class if you change it outside it. This is what JLS (Java 11, paragraph 8.1.3) says about it:

    Any local variable, formal parameter, or exception parameter used but not declared in an inner class must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.

    Link: https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.1.3


    UPD: If you want a workaround though, you can try this:

    int finalWorkaround = clickNumber; 
    
    btn.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            // Our finalWorkaround is effectively final here, so we can use it
            System.out.println(finalWorkaround);
        }
    });
    clickNumber += 1;