CSE190L Notes for Friday, 5/11/07

I said that I wanted to devote several lectures to exploring some important topics that will come up in the Qubic project. In particular, I want to first talk about serialization. Then we'll move on to discussing menus and what are called Action objects.

Java has a powerful built-in mechanism for representing objects in a format that can be saved in a file. This mechanism is known as serialization. This is described in detail in chapter 12 of the Horstmann text. That chapter begins by discussing Java's complex set of stream classes and reader/writer classes. There is a lot of variety. Horstmann refers to it at as a "zoo." Sun did it this way so that it could provide a set of tools that could be applied to many different applications. For example, it is fairly simple to take an application that sends output to a file and adapt it to instead send that output over a network.

For our purposes, we are interested in classes called ObjectOutputStream and ObjectInputStream. We want to use these classes to add new functionality to the Clicker program we looked at earlier in the quarter. I brought up a new version of the ClickerController that included code to add two new buttons: one to save the state of the program and one to restore the previous state. I had filled in the details for adding the button to the frame, but I hadn't yet filled in the action listeners for the two buttons.

So I started with the save button. Our goal was to fill in the action listener:

        save.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            }
        });
We need to construct an ObjectOutputStream and we want it to go to a fille. Unfortunately, the ObjectOutputStream constructor doesn't take a file, it takes an OutputStream. This is one of those cases where we have to combine two different elements of the stream zoo to get what we want. To read data from a file, we need a FileOutputStream. We can then construct an ObjectOutputStream from the FileOutputStream. It's like attaching one kind of pipe to another kind of pipe in plumbing. So we started with this code:

        save.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                FileOutputStream fos = new FileOutputStream("clicker.dat");
                ObjectOutputStream output = new ObjectOutputStream(fos);
                // produce appropriate output
            }
        });
In terms of producing appropriate output, we decided to write out the model. With an ObjectOutputStream, we an accomplish this with a single call on a method called writeObject:

        save.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                FileOutputStream fos = new FileOutputStream("clicker.dat");
                ObjectOutputStream output = new ObjectOutputStream(fos);
                output.writeObject(model);
            }
        });
Unfortunately, this code didn't compile. The logic is correct, but there are checked exceptions that need to be caught. In fact, each of these three lines of code potentially throws a checked exception. So we need to include this in a try/catch block. I mentioned that one of the approaches I like is to re-throw an exception like this as a RuntimeException. Remember that RuntimeExceptions are not checked exceptions. They're exceptions like NullPointerException. So we rewrote this as:

        save.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    FileOutputStream fos = new FileOutputStream("clicker.dat");
                    ObjectOutputStream output = new ObjectOutputStream(fos);
                    output.writeObject(model);
                } catch (IOException error) {
                    throw new RuntimeException(error.toString());
                }
            }
        });
With this change the code compiled. So we tried running it. It worked fine until we clicked on the save button. Then we got a long error that began with this:

Exception in thread "AWT-EventQueue-0" java.lang.RuntimeException:
    java.io.NotSerializableException: ClickerModel
This is our RuntimeException re-throwing an exception it encountered. The problem is that we can only call the writeObject method on objects that implement the Serializable interface. Our ClickerModel does not implement the interface. So we had to modify the header for the ClickerModel:

        public class ClickerModel implements Serializable {
            ...
        }
Then we took a look at the api documentation for the Serializable interface to see what methods we would need to include in the class. Oddly enough, we found that the interface has no methods. It's empty. The Serializable interface is what is known as a tagging interface or marker interface. We use it as a way to identify certain classes as belonging to a specific type. Think of it as a membership club that has no admissions criteria. Anyone who wants to claim to be Serializable is Serializable.

We ran the program again and found that it worked. It created a file called clicker.dat. So then we made a parallel set of changes to the action listener for restore:

        restore.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    FileInputStream fis = new FileInputStream("clicker.dat");
                    ObjectInputStream input = new ObjectInputStream(fis);
                    model = (ClickerModel) input.readObject();
                } catch (Exception error) {
                    throw new RuntimeException(error.toString());
                }
            }
        });
Right around this time I made a decision that sent us into a weird tangent. I pointed out that in my original version of Clicker I had included the enumerated type Player in the ClickerModel class:

        public class ClickerModel {
            public enum Player {COMPUTER, HUMAN};

            ...
        }
This meant that to refer to constants like COMPUTER and HUMAN, we had to say ClickerModel.Player.COMPUTER and ClickerModel.Player.HUMAN. That's sufficiently tedious that I think it's easier to elevate the enumerated type to be outside the model and stand on its own as a separate file. That way we can refer to Player.COMPUTER and Player.HUMAN. I made that change and fixed all of the references to these constants. Everything compiled, but for some reason it kept throwing an odd exception in DrJava. I have since found that this seems to be a bug, so I'm not going to discuss the details here in the notes. Monday's lecture picks up where this one left off.


Stuart Reges
Last modified: Wed May 16 22:29:39 PDT 2007