Search code examples
javainner-classesfinal

Java - access to variable within inner class or assign a value to final variable


I need to create several buttons and upload files. So I want to create a function to set these buttons. However, I'm getting a compilation error inside of my setNewButton.

My code is shown as below:

public class Solution extends JFrame {
    private static final String FILE_NAME_1 = "my file1";
    private File file1;
    private void setNewButton(Container contentPane, final String fileName, String format, File file) {
        contentPane.add(Box.createVerticalStrut(5));
        final Label label = new Label("Select " + fileName + " in ." + format +" format");
        contentPane.add(label);
        contentPane.add(Box.createVerticalStrut(10));
        Button selection = new Button("Select " + fileName);
        contentPane.add(selection);
        selection.addActionListener(new FileSelectionListener("Only " + format + " is allowed", format) {
            @Override
            protected void setSelection(File selectedFile) {
                file = selectedFile;  // compilation error here
                label.setText("Selected" + fileName + selectedFile.getAbsolutePath());
            }
        });
    }

    public uploadFiles() {
        Container contentPane = this.getContentPane();
        setNewButton(contentPane, FILE_NAME_1, "xls", file1);
    }
}

The error is: Variable file is accessed from within inner class, needs to be declared final

I have checked some similar questions in stackoverflow. I know file has to be final like label and fileName here.

However file here could be final since I would like to assign selectedFile to it.

I would like to know if there is any walkaround for this problem.

Any help would be appreciated. :)

Thanks to @M. Prokhorov and @Chang Liu. According to JLS 8.1.3. Inner Classes and Enclosing Instances

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, or a compile-time error occurs where the use is attempted.

So when I tried to send a parameter file inside FileSlectionListener, there will be a compile error. However, the member file1 inside Solution is not local variable, so if I delete the file from my method, there will be no error. So @talex 's anwser is right in this case.

However since my problem is to find a method to pass a File to inner class and assign the variable with selectedFile, I couldn't find a way for it. My workaround is based on @Chang Liu's answer.

My revised code is as below:

public class Solution extends JFrame {
    private static final String FILE_NAME_1 = "my file1";
    private File file1;
    private void setNewButton(Container contentPane, final String fileName, String format) {
        contentPane.add(Box.createVerticalStrut(5));
        final Label label = new Label("Select " + fileName + " in ." + format +" format");
        contentPane.add(label);
        contentPane.add(Box.createVerticalStrut(10));
        Button selection = new Button("Select " + fileName);
        contentPane.add(selection);
        selection.addActionListener(new FileSelectionListener("Only " + format + " is allowed", format) {
            @Override
            protected void setSelection(File selectedFile) {
                setFile(selectedFile, fileName);  // no compilation error here
                label.setText("Selected" + fileName + selectedFile.getAbsolutePath());
            }
        });
    }

    public uploadFiles() {
        Container contentPane = this.getContentPane();
        setNewButton(contentPane, FILE_NAME_1, "xls", file1);
    }

    private void setFile(File file, String fileName) {
        switch (fileName) {
            case FILE_NAME_1:
                sollFile = file;
                break;
            default:
                throw new AssertionError("Unknown File");
        }
    }
}

Still, welcome to give me any advise if you have a better answer. :)


Solution

  • As stated from M. Prokhorov's comment, if you go to the JLS 8.1.3. Inner Classes and Enclosing Instances, you will see it is stated that:

    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, or a compile-time error occurs where the use is attempted.

    Similar rules on variable use apply in the body of a lambda expression.

    So the parameter File file of method setNewButton as a variable is not effectively final in your inner class new FileSelectionListener's method setSelection, i.e., you assigned a new value to this variable, which makes it not effectively final.

    Some workaround to resolve this compile-time error, by defining a setter for file instead of passing an argument (but I am not sure whether this is a best practice):

    public class Solution extends JFrame {
        private static final String FILE_NAME_1 = "Selected SOLL:";
        private File file;
        private void setNewButton(Container contentPane, final String fileName, String format) {
            contentPane.add(Box.createVerticalStrut(5));
            final Label label = new Label("Select " + fileName + " in ." + format +" format");
            contentPane.add(label);
            contentPane.add(Box.createVerticalStrut(10));
            Button selection = new Button("Select " + fileName);
            contentPane.add(selection);
            selection.addActionListener(new FileSelectionListener("Only " + format + " is allowed", format) {
                @Override
                protected void setSelection(File selectedFile) {
                    setFile(selectedFile);  // call file setter here
                    label.setText("Selected" + fileName + selectedFile.getAbsolutePath());
                }
            });
        }
    
        // define a setter for your File member
        private void setFile(File file) {
            this.file = file;
        }
    
        public void uploadFiles() {
            Container contentPane = this.getContentPane();
            setNewButton(contentPane, FILE_NAME_1, "xls");
        }
    }