-1

Hello I am trying to do pretty much the exact same thing as the person who asked this question: Variable is accessed from within inner class needs to be declared final

So I'm going to continue to use their code as a reference in this question:

 File directory = new File(prefs.getString("path",null));
 File[] files = directory.listFiles();
        for (File file :files){
            if(file.isDirectory()) {
                buttons.add(new Button(this));
                Button button = buttons.get(buttons.size() - 1);
                String fileName = file.getName();

                button.setText(fileName);
                button.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Intent intent = new Intent(currentActivity, EOChoiceActivity.class);
                        intent.putExtra("fileExtension",fileName);
                        startActivity(intent);
                    }
                });
                layout.addView(button);
            }
        }

I'm asking the same question as the original author also: why is it that only the last fileName selected is sent for any button? I understand that this has to do with the rules of how an inner class accesses local variables, as cited in JLS 8.1.3, but I still just can't understand the reasoning.

The only thing I can think of that makes sense of this is that the inner class is using a reference of fileName, and so when the fileName value changes on each iteration, and eventually is solidified in it's value on the last iteration, the "final" reference would be to the value of the last element in the files array and this reference would be present for each button.

Yeah I'm just really confused here... I can't understand why the different values for fileName wouldn't apply to each button, but instead only the last fileName value is applied to each button.

Vyres
  • 73
  • 8
  • What "final" file name are you talking about? You are creating multiple buttons, and each button has a different file name as the button text, and that same text is sent as part of the `Intent` when you click the button. If you click button `hello.txt`, then the intent will have `fileExtension = "hello.txt"`. --- If you only see one button, e.g. the one with the last name, then it's because you have the wrong layout in the `layout`, so all the buttons were stacked on top of each other. – Andreas Feb 09 '21 at 23:38

1 Answers1

0

I do not understand the question, neither the original. I do not work with android, but with Java. So I did the following test:

JPanel buttons = new JPanel();
File directory = new File(".");
File[] files = directory.listFiles();
for (File file :files) {
    if (file.isDirectory()) {
        JButton button = new JButton();
        buttons.add(button);
        final String fileName = file.getName();
        button.setText(fileName);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                System.out.println(fileName);
            }
        });
    }
}
JOptionPane.showMessageDialog(null, buttons);

and all is working as expected that is, when pressing any button, I get its text sent to the standard output.

Regarding the original question, there should be no difference if the variable is declared final or not.
Maybe the author got confused since compiler of older versions (before Java 8) would generate an error if such variable was not declared final; with Java 8 or newer, the variable must only be effectively final(document in JLS 8.1.3).

Sure, if fileName was declared as a class field (not a variable), then the last value set would be the one sent for all buttons (the value of fileName when the button is pressed).

If fileName is a variable, then it stops existing when the execution of the block it is in is terminated - so its value must be stored in the created instance(s) of the inner class - it would be confusing if the variable was assigned a new value after the creation of that instance - must the saved value be actualized? Or should the old value be used? I believe that is one of the reason for the requirement of being final. See Jons explanation here - surely better than mine.

  • Can you clarify what you mean by "class field"? – Vyres Feb 10 '21 at 00:46
  • maybe class or instance variable would be better: `public class A { private String fileName; }` (JLS 8.3) –  Feb 10 '21 at 00:50
  • Ah an instance variable. Well assuming if it was a class field (instance variable) why would this mean that the last value set would be the one sent for all buttons, even if the value of the instance variable was changing on each iteration? – Vyres Feb 10 '21 at 00:53
  • because the *instance variable* exists outside the block (loop in this case), so there is no need ot copy its value to the instance of the inner/nested class. The inner class would access the value of it as any other class, so it would see the value of it **when** the button is pressed (the last value set in the loop) –  Feb 10 '21 at 00:56
  • Okay this is really close to answering my question. The only part I can't figure out, is that if your answer was the case, wouldn't the copy made of the instance variable by the inner class be a reference to the instance variable? If the variable was copied by value, then it shouldn't matter if the value of the instance variable is changed later, because the inner class only copied the value of the instance variable and not a reference to it so it can't be "tracked" per say? Basically what you're saying makes perfect sense if the inner class receives a copy of the reference of the instance var – Vyres Feb 10 '21 at 01:02