Reputation: 13
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. :)
Upvotes: 1
Views: 2084
Reputation: 1869
Just extract your action listener to be named inner class of Solution and you will be able to assign Solution.this.file from the inner class :
public class Solution extends JFrame {
private class MyListener extends FileSelectionListener{
@Override
protected void setSelection(File selectedFile) {
Solution.this.file = selectedFile; // NO compilation error here
}
}
private static final String FILE_NAME_1 = "Selected SOLL:";
private File file;
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 MyListener() );
}
public uploadFiles() {
Container contentPane = this.getContentPane();
setNewButton(contentPane, FILE_NAME_1, "xls", file1);
}
}
Upvotes: 0
Reputation: 2697
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");
}
}
Upvotes: 0
Reputation: 20455
You have two variables with name file
. One is class variable and another is method parameter.
Just remove parameter file
from your method and all will work fine.
Upvotes: 2
Reputation:
You can create yourself a mutable wrapper class for the file:
public class FileWrapper {
/** The file. */
private File file;
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
}
Then you can use a final instance of that class:
final private FileWrapper fileWrapper = new FileWrapper();
// ...
selection.addActionListener(new FileSelectionListener("Only " + format + " is allowed", format) {
@Override
protected void setSelection(File selectedFile) {
fileWrapper.setFile(selectedFile);
label.setText("Selected" + fileName + selectedFile.getAbsolutePath());
}
});
And get the last selected file by calling fileWrapper.getFile()
outside of your inner class.
Upvotes: 0