CSE190L Notes for Friday, 4/6/07

I began by saying that I wanted to discuss reflection in Java, which appears at the end of chapter 5. I almost don't want to discuss it because reflection is one of the most abused features of Java. But it's also one of the most powerful, so it's good to be aware that it exists.

On Wednesday we had seen that there is a class called Class that stores information about Java classes. It is part of the java.lang.reflect package. I said that the interactions pane in DrJava is a good way to explore how to use this package. We started with these operations:

        import java.lang.reflect.*;
        String s1 = "hello world!";
        Class c = s1.getClass();
I constructed a second String object that I said would turn out to be interesting:

        String s2 = s1.substring(4, 11);
This represents the String "o world".

You can ask a Class object for the methods or fields of the class, as in:

        c.getMethods()
This call returns an array of Method objects (a value of type Method[]). You can use a for-each loop to print them out:

        for (Method m : c.getMethods()) {
            System.out.println(m);
        }
This produced a very long list of public methods for the String class:

        public int java.lang.String.hashCode()
        public volatile int java.lang.String.compareTo(java.lang.Object)
        public int java.lang.String.compareTo(java.lang.String)
        public boolean java.lang.String.equals(java.lang.Object)
        public int java.lang.String.length()
        public char java.lang.String.charAt(int)
        public java.lang.String java.lang.String.toString()
        public int java.lang.String.indexOf(int)
        ...
We didn't see nearly as much when we asked for public fields:

        for (Field f : c.getFields()) {
            System.out.println(f);
        }
We just saw this:

        public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER
That's because String objects don't have many public fields. We can see all of the fields by calling a slight variation of the method that includes private and protected fields:

        for (Field f : c.getDeclaredFields()) {
            System.out.println(f);
        }
That gave us this list:

        private final char[] java.lang.String.value
        private final int java.lang.String.offset
        private final int java.lang.String.count
        private int java.lang.String.hash
        private static final long java.lang.String.serialVersionUID
        private static final java.io.ObjectStreamField[]
        java.lang.String.serialPersistentFields
        public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER
We can see that it has a field called value that is of type char[] to store the characters of the string. It also has variables for offset, count, and hash. We could guess what these are, but it's hard to know just from the names.

Fortunately, we can poke around even more inside this object. You can ask a field to print its value for a particular object, so we asked it to print the values for our String s1:

        for (Field f : c.getDeclaredFields()) {
            System.out.println(f);
            System.out.println(f.get(s1));
        }
This didn't quite work. It generated an IllegalAccessException. It also wiped out our variables s1, s2 and c, setting them to null. This isn't terribly surprising because these are private fields of the object. You wouldn't expect to be able to access them. The surprising thing is that you can if you know the right incantation. We need to tell each field to be accessible before we try to get the value.

So I reinitialized our variables and tried again:

        s1 = "hello world!";
        c = s1.getClass();
        s2 = s1.substring(4, 11);
        for (Field f : c.getDeclaredFields()) {
            System.out.println(f);
            f.setAccessible(true);
            System.out.println(f.get(s1));
            System.out.println();
        }
This worked, producing the following output:

        private final char[] java.lang.String.value
        [C@b0c5a
        
        private final int java.lang.String.offset
        0
        
        private final int java.lang.String.count
        12
        
        private int java.lang.String.hash
        0
        
        private static final long java.lang.String.serialVersionUID
        -6849794470754667710
        
        private static final java.io.ObjectStreamField[]
        java.lang.String.serialPersistentFields
        [Ljava.io.ObjectStreamField;@4589eb
        
        public static final java.util.Comparator
        java.lang.String.CASE_INSENSITIVE_ORDER
        java.lang.String$CaseInsensitiveComparator@bd4dc2
This was interesting to compare to the corresponding values for s2, which we got by calling the Field object's get with s2 rather than s1:

        for (Field f : c.getDeclaredFields()) {
            System.out.println(f);
            f.setAccessible(true);
            System.out.println(f.get(s2));
            System.out.println();
        }
This produced the following output:

        private final char[] java.lang.String.value
        [C@b0c5a
        
        private final int java.lang.String.offset
        4
        
        private final int java.lang.String.count
        7
        
        private int java.lang.String.hash
        0
        
        private static final long java.lang.String.serialVersionUID
        -6849794470754667710
        
        private static final java.io.ObjectStreamField[]
        java.lang.String.serialPersistentFields
        [Ljava.io.ObjectStreamField;@4589eb
        
        public static final java.util.Comparator
        java.lang.String.CASE_INSENSITIVE_ORDER
        java.lang.String$CaseInsensitiveComparator@bd4dc2
Looking closely at this, you'll see that the two objects refer to the same character array. The field called value is equal to [C@b0c5a for each. This is the ugly output produced by the built in toString method, but you can see that the address information is identical. That's because when you form a substring, Java doesn't copy characters. Instead the two strings share a common character array. You can see that for the s1 the values of offset and count are 0 and 12, indicating that it uses all 12 character starting at position 0. For s2 the values of offset and count are 4 and 7 because it uses 7 characters starting at index 4.

I pointed out that it's a bit odd that hash is equal to 0 for each. This field stores the hash code for the String, but it's certainly not equal to 0. Why do we see a 0? That's because the computation of the hash code is a "lazy" evaluation. Java programs typically manipulate thousands of String objects and we don't need to know the hash code of most of them. So to make Strings more efficient, the computation of the hash value is delayed until it's needed, which is known as lazy evaluation.

We were able to force the evaluation of this by calling the hashCode method:

        s1.hashCode()
This printed the following response:

        -217287203
When we again looked inside the object, we found that the hash field was now equal to this value. So it isn't computed until it's needed and then it is stored so that it doesn't have to be computed again.

In general, you shouldn't be worrying about these kind of optimizations in your own code. Don Knuth has said that "Premature optimization is the root of all evil in programming." This is the kind of thing that you'd do after you have a working program if you discover some kind of bottleneck that you want to fix.

I mentioned that using reflection you can even set private fields. This seems like a very dangerous thing and it is. Java has a notion of a "security manager" that will decide whether or not to allow such operations. I typed this into DrJava to demonstrate that it has no security manager operating:

        System.getSecurityManager()
The response was "null" (no security manager). On my own I had tried setting a security manager, which crashed the interactions pane:

        System.setSecurityManager(new SecurityManager())
In practice, you would tend to have a security manager in place that would prevent potential abuses of reflection. One of the reasons that these powerful operations exist is that you might want to write a debugger in Java. How do you suppose eclipse does what it does? So there certainly are cases where these potentially dangerous operations actually make sense.

Then I turned to chapter 8. We are now going to explore how to handle events in a Java program. This is a different style of programming than we're used to. Instead of having a program that controls what happens, we let the user control what happens. This is known as event driven programming. The idea is to write code that responds to user events. Our main program, then, simply puts up the user interface and our program waits for the user to do something. Our code then responds to those user events.

The Java philosophy is to have user interface components like buttons and panels and frames generate user interface events. It is a multicasting model, which means that you are allowed to attach as many listeners as you want to a user interface component.

We started with some simple code that put three JButton objects into a panel. The idea is to have the buttons change the background color of the panel. We used our standard main class:

        public class FriMain {
            public static void main(String[] args) {
                FriFrame frame = new FriFrame();
                frame.start();
            }
        }
and frame class:

        import javax.swing.*;
        
        public class FriFrame {
            private JFrame frame;
            private FriPanel panel;

            public FriFrame() {
                frame = new JFrame();
                frame.setTitle("Friday fun");
                frame.setSize(300, 300);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
                panel = new FriPanel();
                frame.add(panel);
            }
        
            public void start() {
                frame.setVisible(true);
            }
        }
We spent the rest of our time with the panel class. At first we just added the buttons to the panel:

        import java.awt.*;
        import java.awt.geom.*;
        import javax.swing.*;
        
        public class FriPanel extends JPanel {
            public FriPanel() {
                JButton yellowButton = new JButton("Yellow");
                JButton blueButton = new JButton("Blue");
                JButton redButton = new JButton("Red");
        
                add(yellowButton);
                add(blueButton);
                add(redButton);
            }
        }
When we ran this program, we saw the three buttons appear and we were able to press the buttons. When we did so, they reacted. Keep in mind that an object like a JButton is a fairly sophisticated object that does a lot without you having to do anything yourself. We noticed that as we pressed different buttons, the "focus" shifted from one to the other.

But the buttons didn't do anything because we haven't yet added any listeners of our own. That doesn't mean the events aren't being generated. In fact, a program written with Swing generates all sorts of user interface events. But most of them are ignored unless we write code to listen to them ourselves.

To have the button do something, we need to add what is known as an action listener. This is the most basic kind of listener in AWT. It basically says, "Something happened." The understanding for a button is that this occurs when the button is pressed. To define our own listener, we have to implement an interface known as ActionListener, which looks like this:

        public interface ActionListener {
            public void actionPerformed(ActionEvent e);
        }
So our listener object needs to have this method. The ActionEvent object that we are passed implements what is known as a callback protocol. It's like caller id on a telephone. Someone might leave a message saying, "Call me" and you might have no idea who the caller was. When Java tells you that an action was performed, it passed you an object of type ActionEvent that you can talk to if you need to get more information about exactly who called you and why. For example, the ActionEvent object has a method called getSource that allows you to ask who called you (which button or other component).

In our case, we don't need to use the ActionEvent data because we're going to construct three different listener objects that each perform a very specific task.

To have an object we need a class, so I began by writing this class header:

        class ColorAction implements ActionListener {
            ...
To keep things simple, I defined it in the same class as the panel class but left off the "public" modifier. Java limits you to one public class per file, but allows you to have extra classes if they aren't declared to be public. Horstmann does this a lot in the textbook, although I don't generally recommend it. I am doing it here because eventually this class is going to be included inside the panel class itself.

I asked people what information this class would need to do its job and people said that it would need a reference to the panel and it would need to know what color to set the panel to. These values had to be stored in fields of the class. So we ended up with this definition:

        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);
            }
        }
To get this to compile, we had to import java.awt.event.*. We then modified the panel constructor to construct three different listeners and to attach one to each different button:

        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);
            }
        }
This program did what we were hoping for. When we pressed the yellow button, the panel was set to yellow. When we pressed the blue button, it became blue. And when we pressed the red button, it became red.

I said that we'd look at several variations of this in Monday's lecture as we move deeper into chapter 8 material. In doing so, I will include the material from chapter 6 that we skipped on inner classes and anonymous inner classes.


Stuart Reges
Last modified: Mon Apr 9 10:07:45 PDT 2007