【问题标题】:Java - access to variable within inner class or assign a value to final variableJava - 访问内部类中的变量或为最终变量赋值
【发布时间】:2017-12-08 04:00:53
【问题描述】:

我需要创建几个按钮并上传文件。所以我想创建一个函数来设置这些按钮。但是,我的 setNewButton 内部出现编译错误。

我的代码如下所示:

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);
    }
}

错误是:Variable file is accessed from within inner class, needs to be declared final

我在stackoverflow中检查了一些类似的问题。我知道file 必须像labelfileName 一样是最终的。

但是这里的file 可能是final,因为我想将selectedFile 分配给它。

我想知道这个问题是否有任何解决方法。

任何帮助将不胜感激。 :)

感谢@M。普罗霍罗夫和@Chang Liu。 根据 JLS 8.1.3. Inner Classes and Enclosing Instances

任何使用但未在内部类中声明的局部变量、形参或异常参数必须声明为 final 或实际上是 final,否则在尝试使用时会发生编译时错误。

所以当我尝试在FileSlectionListener 内部发送参数file 时,会出现编译错误。但是,Solution 中的成员file1 不是局部变量,所以如果我从我的方法中删除file,就不会出错。所以@talex 的答案在这种情况下是正确的。

但是,由于我的问题是找到一种将File 传递给内部类并使用selectedFile 分配变量的方法,因此我找不到解决方法。我的解决方法是基于@Chang Liu 的回答。

我修改后的代码如下:

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");
        }
    }
}

不过,如果您有更好的答案,欢迎给我任何建议。 :)

【问题讨论】:

  • 我正在寻找 selectedFile 的声明位置。那是哪里?
  • @markspace 是 setSelection 方法的参数。
  • 请参阅 this JLS 了解您的错误。它基本上归结为 - java 强烈禁止(通过编译错误)匿名内部类(包括 lambdas)“捕获”可能在内部类实例处于活动状态时重新分配的变量。
  • 谢谢@M.Prokhorov 它有帮助。

标签: java inner-classes final


【解决方案1】:

您有两个名称为 file 的变量。一个是类变量,另一个是方法参数。

只需从您的方法中删除参数file,一切都会正常工作。

【讨论】:

  • 不,不会,请参阅我对这个问题的评论。
  • 我只是在输入这个。这绝对是正确答案!将方法参数的名称更改为“文件”以外的名称,编译警告就会消失。
  • @M.Prokhorov 您对 JLS 很了解,但在这种特殊情况下是错误的。该规则不适用于实例变量。
  • @talex,他有隐藏实例变量的方法参数,并且他不使用this.Solution.file,因此编译器假定他尝试访问方法参数,使其不是最终的。实例变量可以避免这种情况,因为实际访问看起来像this.<class>.<name>,所以如果它是最终的,应该考虑的变量是this,就检查而言,这是一个相当安全的选择。
  • @M.Prokhorov 是的。这就是我建议删除参数的原因。
【解决方案2】:

正如M. Prokhorov's comment所说,如果你去JLS 8.1.3. Inner Classes and Enclosing Instances,你会看到它声明:

任何使用但未在内部类中声明的局部变量、形参或异常参数必须声明为 final 或 effectively final,否则在尝试使用时会发生编译时错误。 em>

关于变量使用的类似规则适用于 lambda 表达式的主体。

所以方法setNewButton作为变量的参数File file在你的内部类new FileSelectionListener的方法setSelection中不是effectively final,即你分配了一个新的值到这个变量,这使得它不是 实际上是最终的

通过为file 定义一个设置器而不是传递参数来解决此编译时错误的一些解决方法(但我不确定这是否是最佳做法):

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");
    }
}

【讨论】:

    【解决方案3】:

    你可以自己为文件创建一个可变的包装类:

    public class FileWrapper {
    
        /** The file. */
        private File file;
    
        public File getFile() {
            return file;
        }
    
        public void setFile(File file) {
            this.file = file;
        }
    }
    

    然后您可以使用该类的最终实例:

    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());
            }
        });
    

    并通过在内部类之外调用fileWrapper.getFile() 来获取最后选择的文件。

    【讨论】:

    • 您可能需要将file 包裹在AtomicReference 中以保证线程安全。
    • 为了使这个答案完整,最好先解释一下为什么会出现这个错误。
    • @OH GOD SPIDERS 我喜欢你的回答。但是,会有问题。 setSelection 将无法工作,直到我单击按钮。但是,如果我在内部类之外调用fileWrapper.getFile(),它不会等待内部类调用setFile()。简而言之,fileWrapper.getFile() 在这种情况下将一无所获。
    【解决方案4】:

    只需将您的动作侦听器提取为解决方案的内部类,您就可以从内部类中分配 Solution.this.file :

    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);
        }
    }
    

    【讨论】:

    • 感谢您的回答。但是,这对我来说有点令人困惑。
    • 谢谢你的榜样。
    • @Aleander 我认为有一个小错误。解决方案中有一个private File file,但在uploadFiles 中,您传递了未清除的file1。根据@M。 Prokhorov 和@Chang 的anwser,我发现如果我在FileSelectionListener 中直接使用private File file1,就不会出错。所以我决定用一个开关给Solution中的成员分配不同的selectedFile
    猜你喜欢
    • 1970-01-01
    • 2019-08-19
    • 1970-01-01
    • 1970-01-01
    • 2011-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多