Pass a non-final like a final arg?

I try to build a menu where some item can be change during the session or with data from outside unknow before the coding session. So I try to pass a non-final data but that’s don’t work obviously.
I try to find a way to pass out…but failled.
Below my problem, the first work but not the second when I try with array String:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

JMenuBar menu_bar;
JFrame frame;
String info;
void setup() {
  size(300,300);
  System.setProperty("apple.laf.useScreenMenuBar","true");
  frame = (JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas)this.getSurface().getNative()).getFrame();
  menu_bar = new JMenuBar();
  frame.setJMenuBar(menu_bar);
  JMenu menu = new JMenu("Menu");
  menu_bar.add(menu);
  JMenuItem menu_item = new JMenuItem("test"); 
  menu.add(menu_item);
  frame.setVisible(true);
  
  info = "stuf can change";
  menu_item.addActionListener(new ActionListener() { 
    public void actionPerformed(ActionEvent ae) {
      println("action event:",info); 
    }
  });
}

dont work

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;

JMenuBar menu_bar;
JFrame frame;
String [] info = new String[2];
void setup() {
  size(300,300);
  System.setProperty("apple.laf.useScreenMenuBar","true");
  frame = (JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas)this.getSurface().getNative()).getFrame();
  menu_bar = new JMenuBar();
  frame.setJMenuBar(menu_bar);
  JMenu menu = new JMenu("Menu");
  menu_bar.add(menu);
  JMenuItem []  menu_item = new JMenuItem[2]; 
  menu_item[0] = new JMenuItem("ABC"); 
  menu_item[1] = new JMenuItem("XYZ");
  for(int i = 0 ; i < menu_item.length ; i++) {
     menu.add(menu_item[i]);
  }
 
  frame.setVisible(true);
  
  info[0] = "go to abc";
  info[1] = "go to xyz";
  for(int i = 0 ; i < menu_item.length ; i++) {
    menu_item[i].addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent ae) {
        println("action event:",info[i]); // Cannot refer to the non-final local variable info defined in an enclosing scope
      }
    });
  }
}

Plus when I try to use a temp String like thats, it’s alway the last String is used.

info[0] = "go to abc";
  info[1] = "go to xyz";
  for(int i = 0 ; i < menu_item.length ; i++) {
    parser = info[i];
    menu_item[i].addActionListener(new ActionListener() { 
      public void actionPerformed(ActionEvent ae) {
        
        println("action event:",parser); // return  "go to xyz" for all situation
      }
    });
  }

Let’s start out w/ the basics, shall we? :man_teacher:

  • When we instantiate a class or an interface at the same time we make modifications to them, that’s called anonymous instantiation.
  • Those are local synthetic class instances that, if we dare, can even attempt to access local variables & parameters as well inside their own body.
  • Normally, all local variables & parameters cease to exist when their function returns.
  • However, if any of them happens to be accessed inside a locally-created class’ body, its lifespan then becomes the same as the instances of that synthetic class.
  • Other languages, like JavaScript, call those types of local variables & parameters closures:
2 Likes

Thanks teacher !!! That’s mean i’m lock on this way to code a dynamic menu, or I must find an other way to create an ActionListener(). But the problem if it is, that’s ActionListener()is an interface https://docs.oracle.com/javase/7/docs/api/java/awt/event/ActionListener.html.
You think I must create a class implements ActionListener ?

I’m assuming the String parser was declared as a global field, is that so?

Now I ask, how many parser fields there exist: 1 or more than 1?

The reason why println("action event:", parser); logs the latest value assigned to parser when it’s finally called back is b/c there’s 1 parser field only, not many!

Now let’s talk about workarounds on how to create many final closure variables to be used inside synthetic local classes, shall we?

You can simply declare a local final variable inside the loop in order to copy the current value of the loop’s iterator variable: final int indexClosure = i;

for (int i = 0; i < menu_item.length; ++i) {
  final int indexClosure = i;

  menu_item[i].addActionListener(new ActionListener() { 
    @Override public void actionPerformed(final ActionEvent ae) {
      println("Action event:", info[indexClosure]);
    }
  }
  );
}

Or alternatively, a local final variable that stores the String which should be used inside the callback: final String stringClosure = info[i];

for (int i = 0; i < menu_item.length; ++i) {
  final String stringClosure = info[i];

  menu_item[i].addActionListener(new ActionListener() { 
    @Override public void actionPerformed(final ActionEvent ae) {
      println("Action event:", stringClosure);
    }
  }
  );
}

Notice that in each loop iteration, a new individual final local variable is created to be used inside the anonymously instantiated interface. :ok_hand:

Otherwise, Java’s VM would optimize the code and would only create 1 final local variable for the whole loop. :100:

2 Likes

Well, if you intend to add more stuff to it, that’s definitely the smarter approach. :mage:

But for quick callbacks (especially functional interfaces), anonymous instantiations (or even lambdas if only the current PDE would allow them) are the preferred choice. :wink:

Docs.Oracle.com/en/java/javase/11/docs/api/java.base/java/lang/FunctionalInterface.html

Thx, that’s work perfectly! I hadn’t thought to create the variable final in the loop :frowning: my mistake !