CSE143 Notes for Friday, 11/30/12

I started a new topic. I discussed inheritance and what it can be used for. Before the midterm we talked about the mechanics of inheritance, but we haven't yet talked about where inheritance is helpful.

I started with a simple example. We've seen how to use an ArrayList which has operations like add, get and size, as in:

        List<String> list = new ArrayList<String>();
        list.add("how");
        list.add("are");
        list.add("you?");
        System.out.println("list = " + list);
which produces the following output:

        list = [how, are, you?]
I posed the following question. Suppose that you want a list that behaves slightly differently? In particular, you'd like it to be the case that every time that add is called, it actually adds two of the value to the list. So it should have a kind of stuttering effect. In all other respects, it should behave like a normal list.

How would you make this change? The ArrayList class is part of the Java class libraries, so it would not be easy to change it. But that isn't a problem when you work in an object oriented language. There is a mantra that you'll hear in the object-oriented programming community that we want "additive--not invasive--change." Programmers are very used to the idea of doing surgery on existing code. We call that "invasive change" because it involves cutting open an existing class and rearranging it. This is like doing surgery on a person. Sometimes you have no choice but to perform surgery, but it can be very traumatic to do so. As a result, we want to avoid it if we can.

Inheritance provides a great mechanism for avoiding surgery. We can construct a new class called StutterList that inherits from ArrayList:

        public class StutterList<E> extends ArrayList<E> {
            ...
        }
Just with this simple inheritance declaration, we get a class that behaves just like an ArrayList. Now we can say what is different about our version. We want this version of the list to add everything twice. We can easily define that in terms of the existing "add" in the super class:

        public class StutterList<E> extends ArrayList<E> {
            public boolean add(E value) {
                super.add(value);
                super.add(value);
                return true;
            }
        }
Note: the add method in ArrayList returns a boolean value to indicate whether the add was successful, so we have to replicate that behavior in our version. This is all you have to do to make a variation that adds twice. Remember that when you inherit, you have the option to override a method. In this case, we are overriding the add method to give it a different behavior. But you still have access to the method you are overriding by using the "super" keyword (in this case, calling super.add).

Now we have a class that we can use in place of an ArrayList that will have the stuttering behavior we wanted. For example, if we say:

        List<String> list = new StutterList<String>();
        list.add("how");
        list.add("are");
        list.add("you?");
        System.out.println("list = " + list);
we get the following output:

        list = [how, how, are, are, you?, you?]
The idea is that inheritance is very good at capturing variations. Using inheritance we can make a minor change to an existing class without changing the original class (additive--not invasive--change).

As a second example, I said to suppose that you wanted to have a Point object that remembers how many times it has been translated. How can you do that?

The answer will involve inheritance again. But this time it is not as simple as defining a new method. We'll also need a new data field. To remember the number of times it has been translated, the point will need an integer counter. So it will look like this:

        import java.awt.*;
        
        public class MyPoint extends Point {
            private int count;
        
            ...
        }
I don't have a good short name for this class. I don't want to call it PointThatRemembersHowManyTimesItHasBeenTranslated. So instead I just called it "MyPoint." We'll want to override the translate method. It can call the original translate method because we'll still want to do that original behavior, but we can also increment our counter:

        public void translate(int dx, int dy) {
            count++;
            super.translate(dx, dy);
        }
So now we have a data field to remember this and we've modified the translate method to increment the counter each time it is called. The problem is that this is a private data field that we can't see from the outside. So if we want to allow someone to actually see the value of the counter, we'd have to add a "getter" method:

        public int getTranslateCount() {
            return count;
        }
This provides a pretty good version of the class, although there are a few details that we still have to work out. Here's what we have so far:

        import java.awt.*;
        
        public class MyPoint extends Point {
            private int count;
        
            public void translate(int dx, int dy) {
                count++;
                super.translate(dx, dy);
            }
        
            public int getTranslateCount() {
                return count;
            }
        }
Someone suggested that we need to think about the initialization of the counter. I said that we certainly need to think about it, but in this case, we're okay. Remember that Java initializes all data fields to the 0-equivalent. So the counter will be initialized to 0. But the details I'm thinking about do have to do with the constructing of a Point.

So I mentioned to people a detail of inheritance that we haven't yet discussed. What do you get when you inherit from another class? You inherit the state and behavior of the other class. But not entirely. You inherit most methods from the class that you extend, but not all of them. Someone said "constructors" and I said, "That's right." You don't inherit constructors from a class that you extend.

I decided to use this as an opportunity to review the details of constructors and to discuss how inheritance factors into this. So I asked people what Java does when you don't define any constructors. The answer is that Java defines one for you. We refer to it as the "default" or "paren paren" or "zero argument" constructor. For our class called MyPoint, it would be as if we had included the following method in the class:

        public MyPoint() {
            // nothing to do
        }
But this would be the only constructor for the class. In particular, we wouldn't be able to say something like:

        Point p1 = new MyPoint(8, 17);
I then mentioned that every constructor makes a call on a superclass constructor, whether you do so explicitly or not. The syntax for calling a superclass constructor is very similar to the syntax for one constructor calling another, except for the fact that you use the keyword "super" instead of the keyword "this". For example, if you want to call the default constructor of the superclass, you say:

        super();
We have been writing classes for a long time without including such calls. The reason that works is that if you don't call a superclass constructor, then Java does it for you. And by default it calls the default constructor. So if you don't include a call on the superclass constructor, Java will insert the call above for you.

That means that our MyPoint constructor isn't quite as empty as we had thought. It really does the following:

        public MyPoint() {
            super();
        }
Remember that Java inserted this constructor for us because we didn't define one of our own. What would happen if the Point class did not have a default constructor? In that case, we would have gotten a weird error message:

        MyPoint.java:3: cannot find symbol
        symbol  : constructor Point()
That would seem like an odd error message because we never call Point() anywhere in our code. But it makes sense when you understand that Java is doing it for us. Without a constructor, Java will give us a default constructor. And since we didn't say how to call a superclass constructor, it makes the call super().

The Point class actually does have a default constructor, so we don't get this message. But it's important to remember that this will occur. Many classes do not have a default constructor, and then you must call a superclass constructor.

In our case, we want to have a constructor that takes two integers that specify x and y. Our class doesn't keep track of x and y, that's what the Point class does. So all we want to do is to pass the values up the chain to the Point class' constructor that takes two arguments. We can also initialize the counter just to be clear:

        public MyPoint(int x, int y) {
            super(x, y);
            count = 0;
        }
Now we can construct MyPoint objects with two ints. Unfortunately, now we can no longer construct the origin by asking for a "new MyPoint()". Java defines a default constructor if you don't define any of your own. But if you have even one constructor in your class, then Java assumes that you know what you're doing and that you would have defined a default constructor if you wanted one. So if we really want that for the MyPoint class, we have to specifically include it. In this case, we can call the two-argument constructor in the MyPoint class:

        public MyPoint() {
            this(0, 0);
        }
Putting it all together, here is the definition of MyPoint:

        import java.awt.*;
        
        public class MyPoint extends Point {
            private int count;
        
            public MyPoint() {
                this(0, 0);
            }
        
            public MyPoint(int x, int y) {
                super(x, y);
            }
        
            public void translate(int dx, int dy) {
                count++;
                super.translate(dx, dy);
            }
        
            public int getTranslateCount() {
                return count;
            }
        }
Then we looked at how to extend the Stack class so that it would support an undo command. In particular, we want to be able to undo a push or pop operation with a call on a method called undo. I said that we would also want to have a canUndo method because it will not always be possible to undo an operation.

There were many ideas thrown out about how to solve this problem. Our new class needs to store extra information. Someone pointed out that we could use a Stack to keep track of commands that have been performed. So every time we undo an operation, we can pop off our stack the most recent push or pop command that we performed and we can repeat the operation.

So the basic outline of our solution became:

        public class UndoStack<E> extends Stack<E> {
            private Stack undoStack;
        
            public UndoStack() {
                undoStack = new Stack();
            }
        
            public E push(E value) {
                // perform the normal push, but remember it so we can undo it
            }
        
            public E pop() {
                // perform the normal push, but remember it so we can undo it
            }
        
            public boolean canUndo() {
                return !undoStack.isEmpty();
            }
        
            public void undo() {
                // pop an action off the stack and reverse it
            }
        }
We were surprised that the push command returns a value, but we found in reading over the documentation that it returns the value being pushed onto the stack.

As we explored this, we realized that the easy operation to undo is a push. All we have to do to reverse a push is to pop. But a pop is harder to undo because we have to know the value that was popped from the stack. So our undo stack has to store what kind of operation was performed and has to remember values popped from the stack.

It would be more elegant to introduce some kind of "action" class for keeping track of different kinds of actions, but I said that we can just use simple string values to record what kind of action we have performed. The simple case is the push operation where we just have to remember that we performed a push:

        public E push(E value) {
            super.push(value);
            undoStack.push("push");
            return value;
        }
The harder operation is the pop operation where we have to remember the value being popped. So we want to push two things onto our undo stack: a string to indicate that the operation was a pop operation and the value that was popped. We have to be careful about the order. We need to push the value first and then the string because they are going to come back out of the stack in reverse order:

        public E pop() {
            E value = super.pop();
            undoStack.push(value);
            undoStack.push("pop");
            return value;
        }
The final bit of code is the undo method itself. It is prudent to make sure that it is okay to undo and throw an exception if not. Then we simply pop our undo stack and reverse whatever operation was performed:

        public void undo() {
            if (!canUndo()) {
                throw new IllegalStateException();
            }
            if (undoStack.pop().equals("push")) {
                super.pop();
            } else {
                E value = (E) undoStack.pop();         
                super.push(value);
            }
        }
I mentioned that I would post a variation to the calendar that has both an undo and a redo operation. In the case of a redo, it makes sense to be able to redo any sequence of undo operations that appear in a row. For example, if a user does three undo operations in a row, then you could do three redo operations in a row. But as soon as the user takes another action, you can't redo anything. That's how undo/redo works in real applications like Microsoft Word.


Stuart Reges
Last modified: Sun Dec 2 13:00:51 PST 2012