In a school test I used non final variables inside an anonyme inner class.
On the the school Computer and on my private Computer(using x86 jre1.8.0_45) it is working.
However, on the teachers Laptop Eclipse is showing errors (The variables should use final). He is using jre1.8.0.x version (don't know the exact version).
Any ideas why it is working on my computer and not on his computer?
In this code example the no final object jLabel is used inside the actionPerformed function of the ActionListener:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JLabel;
public class Main {
public Main(String[] args) {
JLabel jLabel = new JLabel();
JButton button = new JButton();
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
jLabel.setText("xyz");
}
});
}
}
Newer java versions are more tolerant in this concern: they only require that they should be "effectively final".
At the end, the difference is not soo big - you only can use variables which you COULD tag with final; you may not modify them.
If you are at that place, you as well can make them "really" final, and it works everywhere.
BTW, on 1.8 with its new lambda syntax you can write more elegantly
button.addActionListener((ActionEvent arg0) -> jLabel.setText("xyz"));
You could define the Listener not as an anonymous class but as a private inner class in order to avoid that problem:
public class Main {
public Main(String[] args) {
JLabel jLabel = new JLabel();
JButton button = new JButton();
button.addActionListener(new MyActionListener(jLabel));
}
private class MyActionListener implements ActionListener {
private JLabel jLabel;
MyActionListener(JLabel jLabel) {
this.jLabel = jLabel;
}
#Override
public void actionPerformed(ActionEvent arg0) {
jLabel.setText("xyz");
}
}
}
This way you can just pass the label (and button if you need) to the constructor of the listener and will be able to use it without any problems.
Related
my question is: how do I get the object of my CustomPanel, so that I am able to access its fields (because in my real programm I have some more fields in there) and also am able to delete it from my ArrayList?
I don't know how I have to implement an ActionListener in the Class Window, to somehow get the Object in my Arraylist, which containes the button that got pressed.
Also I am wondering if I am somehow able to implement an ActionListener in the Class CustomPanel which can influence the behaviour of the Object which is an instance of my Class Window.
I have kind of the following code:
public class Window extends JFrame{
ArrayList<CustomPanel> aLCustomPanel = new ArrayList();
JPanel jp = new JPanel();
public Window() {
for(int i=0;i<5;i++){
aLCustomPanel.add(new CustomPanel());
//here I could put the code from the 1 edit - see below
jp.add(aLCustomPanel.get(i));
}
this.add(jp);
}
public static void main(String args[]){
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new Window().setVisible(true);
}
});
}
}
class CustomPanel extends JPanel {
private JButton button;
public CustomPanel(){
button = new JButton("button");
this.add(button);
}
public JButton getButton(){
return this.button;
}
}
my Code is much longer and weirder, so I tried to extract the (for this question) importing things.
Thanks for any help in advance!
edit:
for example: I would like to delete the object from the ArrayList, of which the button got pressed.
//imagine this comment in above code
aLCustomPanel.get(aLCustomPanel.size()-1).getButton().addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
button_IwantToDeleteYou(e); //here I want to remove the panel, containing the button that got pressed from the above ArrayList, which is located in Class Window
}
});
edit2:
added a missing bracket and fixed some mistakes, code should be ok now.
Your code contained a few "gaps", i.e. missing code, which I filled in, as follows:
Added calls to [JFrame] methods setDefaultCloseOperation() and pack() and setLocationByPlatform(). I suggest you refer to the javadoc for those methods in order to understand what they do.
I set a layout manager for jp class member variable in your Window class.
Yes, you need to register an ActionListener with the JButton in class CustomPanel and that listener should reside in your Window class - the one that extends JFrame.
Here is my rewrite of your code. Note that I changed the name of class Window to CusPanel so as to distinguish between your class and java.awt.Window class. Not that it makes a difference, I just prefer not to use names of classes from the JDK.
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class CusPanel extends JFrame implements ActionListener {
private static final int COUNT = 5;
private ArrayList<CustomPanel> aLCustomPanel = new ArrayList<>();
private JPanel jp = new JPanel(new GridLayout(0, COUNT));
public CusPanel() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
for (int i = 0; i < COUNT; i++) {
aLCustomPanel.add(new CustomPanel(this));
// here I could put the code from the 1 edit - see below
jp.add(aLCustomPanel.get(i));
}
this.add(jp);
pack();
setLocationByPlatform(true);
}
public void actionPerformed(ActionEvent actionEvent) {
Object source = actionEvent.getSource();
if (source instanceof JButton) {
JButton button = (JButton) source;
Container parent = button.getParent();
jp.remove(parent);
jp.invalidate();
jp.repaint();
pack();
// aLCustomPanel.remove(parent); <- optional
}
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new CusPanel().setVisible(true);
}
});
}
}
class CustomPanel extends JPanel {
private JButton button;
public CustomPanel(ActionListener parent) {
button = new JButton("button");
button.addActionListener(parent);
this.add(button);
}
public JButton getButton() {
return this.button;
}
}
Note that after removing a CustomPanel, the GUI components need to be laid out again and the JFrame should also be resized accordingly. Hence in the actionPerformed() method, I call invalidate(), then repaint() and then pack(). I also think that if you remove a CustomPanel from the GUI, you should also remove it from the ArrayList, but hey, I still don't understand why you want to do this although I obviously don't know the whole story behind you wanting to do this in the first place.
Of-course, since each button (and each CustomPanel) looks exactly the same, you can't really know which button was removed. Again, I assume you see the big picture whereas I don't.
I've been a lurker on stackoverflow for some time. I'm teaching myself Java, so bear with me if this is a rather elementary question (I couldn't find the answer on here though).
If I have a java class (like the one below), which I wish to use in future classes in a slightly different way (e.g. changing the button text/or output), is there a way to do this by extending the original class?
In the case below I have a JFrame with two buttons which print different text to the console. I simply want to extend this class whilst changing one of the button names.
ORIGINAL CLASS:
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class FrameIt extends JFrame implements ActionListener {
private static final long serialVersionUID = 1L;
public static void main(String[] args) {
new FrameIt().setVisible(true);
}
public FrameIt() {
super("Make a choice");
setSize(600, 600);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new GridLayout());
JButton button = new JButton("Click Me.");
JButton button2 = new JButton("No, you should Click Me!");
button.addActionListener(this);
button2.addActionListener(this);
add(button);
add(button2);
}
#Override
public void actionPerformed(ActionEvent e) {
String name = e.getActionCommand();
if(name.equals("Click Me")){
System.out.println("That was actually the right choice.");
}else{
System.out.println("Poor choice.");
}
}
}
CLASS THAT EXTENDS:
import javax.swing.JButton;
public class Alterations extends FrameIt{
private static final long serialVersionUID = 1L;
public static void main(String args[]){
new Alterations().setVisible(true);
System.out.println("Doing it");
}
public Alterations(){
JButton button2 = new JButton("Slightly different button");
}
}
Thanks.
Creating the JButton instance in the sub-class Alterations will make no difference, since you don't connect it to anything.
One way to handle it is to change the constructor of the original class. You can make the name of the button originate from a method call :
Change :
JButton button2 = new JButton("No, you should Click Me!");
To :
JButton button2 = new JButton(getSecondButtonName ());
Then getSecondButtonName() can return "No, you should Click Me!" in the original class and you can override it in the sub-class to return "Slightly different button".
An alternative would be to remove the original button in the constructor of the sub-class and add the new button.
These solutions make sense only if there's a real reason to create a sub-class in the first place. If the only difference between the base class and the sub-class is the name of one button, there's no reason to create a sub-class.
I followed a few different tutorials on how to get this working and I just can't seem to get my buttons to update the JLabel. Can you tell me which part is incorrect and lead me in the right path of what to fix. This has been plaguing me for hours.
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.SwingConstants;
public class CoinApp extends JFrame {
private JLabel label;
private JRadioButton rdbtnNewRadioButton_3, rdbtnNewRadioButton, rdbtnNewRadioButton_1, rdbtnNewRadioButton_2;
public CoinApp() {
setBounds(50, 50, 500, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
ButtonGroup buttonGroup = new ButtonGroup();
JPanel panel = new JPanel();
getContentPane().add(panel);
JRadioButton rdbtnNewRadioButton_3 = new JRadioButton("");
buttonGroup.add(rdbtnNewRadioButton_3);
rdbtnNewRadioButton_3.setIcon(new ImageIcon(getClass().getResource("UsCent.png")));
panel.add(rdbtnNewRadioButton_3);
JRadioButton rdbtnNewRadioButton = new JRadioButton("");
buttonGroup.add(rdbtnNewRadioButton);
rdbtnNewRadioButton.setIcon(new ImageIcon(getClass().getResource("UsNickel.png")));
panel.add(rdbtnNewRadioButton);
JRadioButton rdbtnNewRadioButton_1 = new JRadioButton("");
buttonGroup.add(rdbtnNewRadioButton_1);
rdbtnNewRadioButton_1.setIcon(new ImageIcon(getClass().getResource("UsDime.png")));
panel.add(rdbtnNewRadioButton_1);
JRadioButton rdbtnNewRadioButton_2 = new JRadioButton("");
buttonGroup.add(rdbtnNewRadioButton_2);
rdbtnNewRadioButton_2.setVerticalAlignment(SwingConstants.TOP);
rdbtnNewRadioButton_2.setIcon(new ImageIcon(getClass().getResource("UsQuarter.png")));
panel.add(rdbtnNewRadioButton_2);
rdbtnNewRadioButton_3.setSelected(true);
label = new JLabel("CENT w:2.5 d:19.1", JLabel.CENTER);
add(label);
}
**
public void actionPerformed(ActionEvent evt) {
if ( rdbtnNewRadioButton_3.isSelected() ) {
label.setText("CENT w:2.5 d:19.1");
}
else if ( rdbtnNewRadioButton.isSelected() ) {
label.setText("NICKEL w:5.0 d:21.2");
}
else if ( rdbtnNewRadioButton_1.isSelected() ) {
label.setText("DIME w:2.3 d:17.9");
}
else if ( rdbtnNewRadioButton_2.isSelected() ) {
label.setText("QUARTER w:5.7 d:24.3");
}
} // end actionPerformed()
**
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
CoinApp frame = new CoinApp();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
for( Coin el : Coin.values())
{
System.out.println(el);
}
}
}
Start by taking a look at How to Write an Action Listeners
In order for your buttons to provide notification, you must register an ActionListener within them.
In order for your buttons to call your actionPerformed method however, you need to implement the ActionListener interface...
public class CoinApp extends JFrame implements ActionListener {
//...
#Override
public void actionPerformed(ActionEvent evt) {
You should add the #Override annotation to methods you believe you are overriding from parent classes or interfaces, it will give you a compiler warning if you've done something wrong, like misspelt it for example.
Now you can register the ActionListener with your buttons...
rdbtnNewRadioButton_3.addActioinListener(this);
Updated...
Also, beware, you are shadowing your variables...
What I mean by this is, you are declaring your variables as instance/class variables...
private JRadioButton rdbtnNewRadioButton_3, rdbtnNewRadioButton, rdbtnNewRadioButton_1, rdbtnNewRadioButton_2;
Which is good, but in your constructor, you are re-declaring them...
public CoinApp() {
//...
JRadioButton rdbtnNewRadioButton_3 = new JRadioButton("");
This means when you go to reference rdbtnNewRadioButton_3 somewhere else in your program, it will be null.
You should be using something like...
public CoinApp() {
//...
rdbtnNewRadioButton_3 = new JRadioButton("");
You need to hook up your event handler method (actionPerformed) to each of the radio buttons. To do this you need to register an ActionListener to each radio button. The most convenient way for you to do this is to have your main class implement the ActionListener interface, and then call addActionListener on each radio button a reference to this
Also note that your event handler method will currently cause you problems because you are never actually setting the class level members (fields) (rdbtnNewRadioButton_3 etc). You are actually creating new local variables in your constructor with the same name that 'hide' the class level fields, which then become inaccessible when they fall out of scope, leaving the class level variables null.
Your constructor code should look like this:
rdbtnNewRadioButton_3 = new JRadioButton("");
Instead of
JRadioButton rdbtnNewRadioButton_3 = new JRadioButton("");
Good luck!
Here is my class:
public class ButtonPanel extends JPanel {
public ButtonPanel () {
makeButton ("button1");
makeButton ("button2");
makeButton ("button3");
}
void makeButton (String name) {
JButton button =new JButton(name);
add(button);
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setText("I was clicked");
}
});
}
}
When the button is clicked, its text should change to "I was clicked". However, I don't know how to access the setText method. I tried button.setText("I was clicked") but this is not possible.
By making the reference of JButton as final, like :
void makeButton(String name)
{
final JButton button =new JButton(name);
add(button);
button.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
button.setText("I was clicked");
}
});
}
Here is one related example :
Add action to a Button created by another Button
EDIT 1:
Here is one updated version of your code, that works as mentioned before :
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ButtonExample1 extends JPanel {
private JButton makeButton (String name) {
final JButton button =new JButton(name);
add(button);
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
button.setText("I was clicked");
}
});
return button;
}
private void displayGUI() {
JFrame frame = new JFrame("Button Example");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JPanel contentPane = new JPanel();
contentPane.add(makeButton("One"));
contentPane.add(makeButton("Two"));
contentPane.add(makeButton("Three"));
frame.setContentPane(contentPane);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
#Override
public void run() {
new ButtonExample1().displayGUI();
}
};
EventQueue.invokeLater(runnable);
}
}
EDIT 2 :
Here is the answer that tries to explain a reason, why you need to declare it as final
To answer your question, you need to understand the basics, as to how the JVM use to work.
When the classes are compiled which contain inner classes, the byte code which gets generated does not actually implement inner classes as a class within a class.
WHY THE ERROR : The local variable was accessed from an inner class, needs to declare it final
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JMenu;
import javax.swing.JPanel;
public class foo extends JPanel
{
public foo()
{
final JMenu edit = new JMenu();
edit.getItem(0).addMouseListener(new MouseAdapter(){
#Override
public void mouseClicked(MouseEvent e)
{
if (e.getClickCount() == 1) {
edit.getItem(0).setEnabled(true);
}
}
});
}
}
When you compile your this program, two files will be created, Foo.class and Foo$1.class. So now your problem comes, since the Second class i.e. foo$1.class doesn't know that Variable edit is present inside the First class i.e. foo.class.
So how to solve this problem ? What JVM does, is that, It makes a requirement for the developer to make the variable of an outer class to be declared as final.
Now this being done, now JVM quietly places a hidden variable with the name val$edit inside the 2nd compiled class file, here is the output as got from javap
Ouput for foo.class
C:\Mine\JAVA\J2SE\folder>javap foo.class
Compiled from "foo.java"
public class foo extends javax.swing.JPanel {
public foo();
}
Now since, edit is local to the constructor, hence the output as above.
C:\Mine\JAVA\J2SE\folder>javap foo$1.class
Compiled from "foo.java"
class foo$1 extends java.awt.event.MouseAdapter {
final javax.swing.JMenu val$edit;
final foo this$0;
foo$1(foo, javax.swing.JMenu);
public void mouseClicked(java.awt.event.MouseEvent);
}
The Variable val$edit is assigned the same value which has been assigned to edit since now the compiler knows that the value cannot be changed as it has been declared final and hence it works this time.
Now what if I change the edit Variable from being Local to being Instance. Now the object of the class knows everything about this variable edit, if it gets changed. So changing the above program likewise we get :
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JMenu;
import javax.swing.JPanel;
public class foo extends JPanel
{
JMenu edit = new JMenu();
public foo()
{
edit.getItem(0).addMouseListener(new MouseAdapter(){
#Override
public void mouseClicked(MouseEvent e)
{
if (e.getClickCount() == 1) {
edit.getItem(0).setEnabled(true);
}
}
});
}
}
Here in this case, we are not suppose to declare and define it as being final, because in this case since the Variable being Local to the whole class, the Variable is send to the Inner Class along with the Object Reference i.e. this
C:\Mine\JAVA\J2SE\folder>javap foo.class
Compiled from "foo.java"
public class foo extends javax.swing.JPanel {
javax.swing.JMenu edit;
public foo();
}
Here is how the Variable is send in this case i.e. this$0 :
C:\Mine\JAVA\J2SE\folder>javap foo$1.class
Compiled from "foo.java"
class foo$1 extends java.awt.event.MouseAdapter {
final foo this$0;
foo$1(foo);
public void mouseClicked(java.awt.event.MouseEvent);
}
Seems like that the interpretation, how this situation works, according to me.
Just now I found this wonderful explanation on the internet regarding Mystery of Accessibility in Local Inner Classes, might be this will help you understand the situation in a much better way :-)
This is a java program with two buttons used to change an integer value and display it.
However in IntelliJIDEA the two lines with
increase.addActionListener(incListener());
decrease.addActionListener(decListener());
keep displaying errors 'Method call expected'.
I am not sure what to do to fix this.
Any help will be greatly appreciated
Thanks
Note: the full code is attached below.
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Main extends JDialog {
public JPanel contentPane;
public JButton decrease;
public JButton increase;
public JLabel label;
public int number;
public Main() {
setContentPane(contentPane);
setModal(true);
increase = new JButton();
decrease = new JButton();
increase.addActionListener(incListener());
decrease.addActionListener(decListener());
number = 50;
label = new JLabel();
}
public class incListener implements ActionListener {
public void actionPerformed (ActionEvent event) {
number++;
label.setText("" + number);
}
}
public class decListener implements ActionListener {
public void actionPerformed (ActionEvent event) {
number--;
label.setText("" + number);
}
}
public static void main(String[] args) {
Main dialog = new Main();
dialog.pack();
dialog.setVisible(true);
System.exit(0);
}
}
incListener and declListener are classes, not methods.
Try
increase.addActionListener(new incListener());
btw, rename your classes names to make them start with an uppercase
It's simple: use new incListener() instead of incListener(). The later is trying to call a method named incListener, the former creates an object from the class incListener, which is what we want.
incListener and decListener are a classes but not a methods, so you must call new to use them, try this:
increase.addActionListener(new incListener());
decrease.addActionListener(new decListener());
sorry for my bad english
substitute the lines with
increase.addActionListener( new incListener());
decrease.addActionListener( new decListener());
Make these changes:
public Main() {
contentPane = new JPanel();
setContentPane(contentPane);
setModal(true);
increase = new JButton("inc");
decrease = new JButton("dec");
contentPane.add(increase);
contentPane.add(decrease);
increase.addActionListener(new incListener());
decrease.addActionListener(new decListener());
number = 50;
label = new JLabel(number+"");
contentPane.add(label);
}
It's sad but I had to Google this same error... I was staring at a method that returned a class. I left off the new operator.
return <class>(<parameters>)
vs
return new <class>(<parameters>)
Whenever a string object is created using new operator a new object is created which is what your program is looking for.
The following link is useful in learning about the difference between a string and a new string.
What is the difference between "text" and new String("text")?