CSE190L Notes for Monday, 4/9/07

In Friday's lecture we were looking at a variation of a program that appears at the beginning of chapter 8 of the textbook. We ended up with a panel class that had three buttons in it and a separate ColorAction class that implemented an ActionListener for the buttons. I said that I wanted to explore several variations of this class as a way to understand some features of Java and to understand the motivation behind them. We're moving into material that is covered in chapter 6 of the book in the section on inner classes.

The first thing I did was to move the ColorAction class into the panel class. In doing so, I declared it to be private and static:

        import java.awt.*;
        import java.awt.geom.*;
        import javax.swing.*;
        import java.awt.event.*;
        
        public class FriPanel extends JPanel {
            public FriPanel() {
                JButton yellowButton = new JButton("Yellow");
                JButton blueButton = new JButton("Blue");
                JButton redButton = new JButton("Red");
        
                yellowButton.addActionListener(new ColorAction(this, Color.YELLOW));
                blueButton.addActionListener(new ColorAction(this, Color.BLUE));
                redButton.addActionListener(new ColorAction(this, Color.RED));
        
                add(yellowButton);
                add(blueButton);
                add(redButton);
            }

            private static class ColorAction implements ActionListener {
                private JPanel panel;
                private Color c;
            
                public ColorAction(JPanel panel, Color c) {
                    this.panel = panel;
                    this.c = c;
                }
            
                public void actionPerformed(ActionEvent e) {
                    panel.setBackground(c);
                }
            }
        }
In doing this, we basically just change the scope of the class. By making it private, we guarantee that only the panel class can see it. Making it static means that it will have the same kind of status as before. This is simply one class declared inside another without any special connection between them.

Then I discussed what happens when we remove the "static" keyword. Then we end up with what are known as "inner classes." Java does a lot of work behind the scenes to give inner classes extra power. In particular, we can eliminate the panel parameter from the ColorAction constructor:

        public class FriPanel extends JPanel {
            public FriPanel() {
                JButton yellowButton = new JButton("Yellow");
                JButton blueButton = new JButton("Blue");
                JButton redButton = new JButton("Red");
        
                yellowButton.addActionListener(new ColorAction(Color.YELLOW));
                blueButton.addActionListener(new ColorAction(Color.BLUE));
                redButton.addActionListener(new ColorAction(Color.RED));
        
                add(yellowButton);
                add(blueButton);
                add(redButton);
            }

            private static class ColorAction implements ActionListener {
                private Color c;
            
                public ColorAction(Color c) {
                    this.c = c;
                }
            
                public void actionPerformed(ActionEvent e) {
                    setBackground(c);
                }
            }
        }
Because the ColorAction class is inside the panel class, it will automatically have access to the methods and fields of the outer class. In particular, the ColorAction class can include a call on setBackground that is really calling a method of the outer panel class. This is a fairly unusual thing that is going on here. Think about this line of code from the ColorAction class:

        setBackground(c);
Normally Java would interpret this as:

        this.setBackground(c);
When we changed it to that, it stopped compiling. That's because the ColorAction class has no method called setBackground. What Java does is that it first tries to interpret any line of code like the one above using the normal "this." interpretation. If the class has such a method, that's the one that gets called. But if it doesn't have such a method, then it sees whether the outer class has such a method. If so, then that's the method that gets called.

This is even more interesting when you think about the fact that there might be more than one panel object. So how does it know which one to use? The answer is that Java uses a mechanism very similar to what we had in our original ColorAction class. We had to pass a reference to "this" when we constructed a ColorAction object from inside of our panel so that the object would know which panel constructed it. With an inner class, Java does this for you. Every time it constructs an object of the inner class, it keeps track of which object of the outer class constructed it and it keeps a reference to that object. In fact, if you ever do need to refer to this companion object, you can use the following notation to do so:

        FriPanel.this.setBackground(c);
By saying "FriPanel.this" you are saying, "Get me the object of the outer FriPanel class that you are linked to."

It turns out that these named inner classes like ColorAction can even be moved inside of a method, although then you can't use modifiers like public, private or static. Horstmann gives an example of this in chapter 8, although Java programmers rarely use that feature. The more useful feature is to define what is known as an anonymous inner class. Consider, for example, this line of code:

        yellowButton.addActionListener(new ColorAction(Color.YELLOW));
This constructs a ColorAction object that implements the ActionListener interface. We tried changing it to this:

        yellowButton.addActionListener(new ActionListener());
That generated a compiler error because ActionListener is an interface and can't be instantiated. But Java allows you to do something rather interesting. After the call on new, you can include a set of curly braces that contain a class definition:

        yellowButton.addActionListener(new ActionListener() {//here} );
When Java sees a call on new followed by these curly braces, it understands it to be a class definition. Because the call on new was calling an interface, it understands this to be a definition of a class that implements the interface. in other words, it interprets this line of code to mean, "I want you to make a new class that implements action listener and that has the body I've included inside curly braces and give me one instance of that class." Notice that you never name the class. You just give a definition for th body of the class.

If we add some indentation to the line of code above, we get:

        yellowButton.addActionListener(new ActionListener() {
            // define the class here
        });
Because this appears on several lines, it seems different from what we had before, but Java doesn't think of it that way. From Java's point of view, this whole thing is a call on the addActionListener method (that's why it ends with a right-paren and a semicolon). It just happens to have space in the middle for a class definition.

Inside we should define the body of the class. All we need is to define the actionPerformed method with an appropriate definition:

        yellowButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setBackground(Color.YELLOW);
            }
        });
We gave similar definitions for the blue and red buttons:

        blueButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setBackground(Color.BLUE);
            }
        });

        redButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setBackground(Color.RED);
            }
      });
Once we had done this, we didn't need the ActionListener class. Instead, we've replaced it with three anonymous inner classes. You'll see when you compile programs like these that you end up with class files that have names like FriPanel$1.class.

Then someone pointed out that this is very redundant, so we explored how to rewrite this using a method. We first tried this code:

        public FriPanel() {
            addButton("Yellow", Color.YELLOW);
            addButton("Blue", Color.BlUE);
            addButton("Red", Color.Red);
        }

        public void addButton(String text, Color c) {
            JButton button = new JButton(text);
            button.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    setBackground(c);
                }
            });
            add(button);
    }
This almost worked, but we got an error message that the local variable c would have to be declared to be final. The problem is that we are referring inside our anonymous inner class to a local variable (a parameter). Local variables exist only while a method is executing. To allow the inner class to access it would be very difficult. So Java allows you to access such a value only if it is declared to be a constant (in other words, if it's not a variable at all). We added the word "final" to the parameter, and then it worked.

Then I mentioned the fact that I don't like having these buttons inside the colored panel. So I showed people how we could include this in the frame by adding these buttons to their own panel. Remember that JPanels are used both for graphics (as a canvas) and for grouping elements together (in this case, putting the three buttons into their own container). I modified the addButton method to instead return a button and wrote this code:

        JPanel p = new JPanel();
        p.add(makeButton("Yellow", Color.YELLOW));
        p.add(makeButton("Blue", Color.BLUE));
        p.add(makeButton("Red", Color.RED));
        frame.add(p, BorderLayout.NORTH);
We had to be careful in adding this new panel. We had to say that we want it to the "north" of the frame (on top).

I spent the last few minutes of class modifying the panel so that it would draw some text that would switch back and forth between white and black. We had to include a boolean field in the panel class to keep track of this. Then in the frame class we added a mouse listener to the panel that would switch the text color. To allow the panel to communicate with the panel, we had to introduce a new public method in the panel called switchTextColor that the frame could call when the mouse was clicked. These changes are included in the handout that I passed out (handout #7).


Stuart Reges
Last modified: Wed Apr 11 12:19:37 PDT 2007