CSE143 Notes for Friday, 3/2/18

Hunter Schafer gave a guest lecture, but I include below notes from a previous version of the lecture.

I discussed two different topics in one lecture. I gave abbreviated versions of each. I include the full lectures for those topics in case anyone wants to see the slower development.

First I said that I wanted to finish up our discussion of the ArrayIntList class. We ended earlier in the quarter with a pretty good version of the class, but I mentioned that there is at least one important method that we were missing.

I asked people to consider the situation where a client wants to replace a value at a particular location. The only option we have given the client is to remove the value and then to add a new value back in its place. This requires shifting values twice, which can be very inefficient if the list is long and if the change occurs towards the front of the list. So I said that we would include a method called set that can be used to replace the value at a given inde:

        public void set(int index, int value) {
	    elementData[index] = value;
        }
Of course, we have to indicate the precondition on the index and we have to check the index to make sure it is legal. We introduced a private method called checkIndex that performs the check for us:

    // pre : 0 <= index < size()
    // post: replaces the integer at the given index with the given value
    public void set(int index, int value) {
        checkIndex(index);
        elementData[index] = value;
    }
I mentioned that the new version also has methods clear, which makes the list empty, removeAll, and isEmpty.

Then I said I wanted to discuss how to implement an iterator. Recall that an iterator as having three basic operations:

I gave an example of client code we might want to write:

	Iterator<Integer> i =  list.iterator();
	int product = 1;
	while (i.hasNext()) {
	    int next = i.next();
	    product = product * next;
	}
	System.out.println("product = " + product);
this variation of the code prints each value and removes any occurrences of values that are multiples of 3:

	Iterator<Integer> i =  list.iterator();
	int product = 1;
	while (i.hasNext()) {
	    int next = i.next();
	    product = product * next;
            if (next % 3 == 0) {
                i.remove();
            }
	}
	System.out.println("product = " + product);
This code examines each value in the list and removes all the multiples of 3.

Then we spent some time discussing how the ArrayIntListIterator is implemented. The main function the iterator performs is to keep track of a particular position in a list, so the primary field will be an integer variable for storing this position:

        public class ArrayIntListIterator {
            private int position;

            public ArrayIntListIterator(?) {
                position = 0;
            }

            public int next() {
                position++;
            }

            ...
        }
I asked people how we would implement hasNext and someone said we'd have to compare the position against the size of the list. I then said, "What list?" Obviously the iterator also needs to keep track of which list it is iterating over. We can provide this information in the constructor for the iterator. So the basic outline became:

        public class ArrayIntListIterator {
            private ArrayIntList list;
            private int position;

            public ArrayIntListIterator(ArrayIntList list) {
                position = 0;
                this.list = list;
            }

            public int next() {
                use get method of list & position
                position++;
            }

            public boolean hasNext() {
                check position against size
            }

            ...
        }
We briefly discussed how to implement remove. We have to keep track of when it's legal to remove a value. Recall that you can't remove before you have called get and you can't call remove twice in a row. We decided that this could be implemented with a boolean flag inside the iterator that would keep track of whether or not it is legal to remove at any given point in time. Using this flag, we can throw an exception in remove if it is not legal to remove at that point in time:

        public class ArrayIntListIterator implements Iterator<Integer> {
            private ArrayIntList list;
            private int position;
            private boolean removeOK;
        
            public ArrayIntListIterator(ArrayIntList list) {
                position = 0;
                this.list = list;
                removeOK = false;
            }
        
            public int next() {
                use get method of list & position
                position++
                removeOK = true;
            }
        
            public boolean hasNext() {
                check position against size
            }
        
            public void remove() {
                if (!removeOK)
                    throw new IllegalStateException()
                call remove method on list
                removeOK = false;
            }
        }
This is a fairly complete sketch of the ArrayIntListIterator code. The calendar includes a complete version. You will notice some odd details that will make more sense after we have learned more about the collections framework (e.g., the class implements the Iterator<Integer> interface and the next method returns a value of type Integer instead of int).

Then I discussed the fact that the new version of the list "grows" the list as needed if it runs out of capacity. It isn't, in general, easy to make an array bigger. We can't simply grab the memory next to it because that memory is probably being used by some other object. Instead, we have to allocate a brand new array and copy values from the old array to the new array. This is pretty close to how shops and other businesses work in the real world. If you need some extra space for your store, you can't generally break down the wall and grab some of the space from the store next door. More often a store has to relocate to a larger space.

The new version of ArrayIntList has this functionality built into it. In the previous version we manually checked the capacity and threw an exception if the array wasn't big enough. In the new version that has been replaced by an ensureCapacity method that constructs a new array if necessary.

Obviously you don't want to construct a new array too often. For example, suppose you had space for 1000 values and found you needed space for one more. You could allocate a new array of length 1001 and copy the 1000 values over. Then if you find you need space for one more, you could make an array that is 1002 in length and copy the 1001 old values over. This kind of growth policy would be very expensive.

Instead, we do something like doubling the size of the array when we run out of space. So if we have filled up an array of length 1000, we double its size to 2000 when the client adds something more. That makes that particular add expensive in that it has to copy 1000 values from the old array to the new array. But it means we won't need to copy again for a while. How long? We can add another 999 times before we'd need extra space. As a result, we think of the expense as being spread out or "amortized" over all 1000 adds. Spread out over 1000 adds, the cost is fairly low (a constant).

You will find that the built-in ArrayList class does something similar. The documentation is a little coy about this saying, "The details of the growth policy are not specified beyond the fact that adding an element has constant amortized time cost." If you look at the actual code, you'll find that increase by 50% each time (a multiplier of 1.5).

The latest version of the ArrayIntList class along with the ArrayIntListIterator class are included in the calendar for this lecture.

Then I discussed inheritance and what it can be used for. 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("four");
        list.add("score");
        list.add("and");
        list.add("seven");
        list.add("years");
        list.add("ago");
        System.out.println("list = " + list);
which produces the following output:

        list = [four, score, and, seven, years, ago]
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 StutteredList that inherits from ArrayList:

        public class StutteredList<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 StutteredList<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 StutteredList<String>();
        list.add("four");
        list.add("score");
        list.add("and");
        list.add("seven");
        list.add("years");
        list.add("ago");
        System.out.println("list = " + list);
we get the following output:

        list = [four, four, score, score, and, and, seven, seven, years, years, ago, ago]
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).

I then mentioned that the "killer app" for inheritance that convinced many people to use it was user interface. Once people started writing GUIs (Graphical User Interfaces), they found that they had to write a lot of detailed code for buttons, text boxes, windows, scroll bars and so on. Inheritance provided an easy mechanism to write the common code once and then to define lots of variations through inheritance.

I then spent some time showing how this works in Java. I warned people that I was showing "old" Java as it originally worked. Java has since evolved and the preferred techniques are more complex than what I showed. I pointed out that there is a Frame class in Java that creates a top-level user interface frame. Even without inheritance, we were able to create one and set various properties by writing code like this:

        import java.awt.*;

        public class DrawFrame {
            public static void main(String[] args) {
                Frame f = new Frame();
                f.setSize(400, 400);
                f.setTitle("Oooooh!");
                f.setBackground(Color.CYAN);
                f.setVisible(true);
            }
        }
Then we came upon the issue of how to make it draw something inside the frame. The metaphor in Java is painting, that you paint things on the screen, and the Frame class has a method called "paint". How do we change how it works? That's where inheritance comes in. We define our own CustomFrame class that extends the Frame class and overrides the paint method. At first we just put a simple println in this method:

        import java.awt.*;

        public class CustomFrame extends Frame {
            public void paint(Graphics g) {
                System.out.println("in paint");
            }
        }
Then we changed the main program so that it constructs a CustomFrame object instead of a Frame object:

        Frame f = new CustomFrame();
It didn't seem like this would work because I never called the paint method. But it actually did work. When I did things like minimizing the window and then restoring it, we saw the println happening. That's because there is a vast amount of code written for us that takes care of details like when to repaint a frame. That's the whole point of this exercise. We know that someone at Sun has written tons of code that we want to take advantage of. Through inheritance we can make tiny additive changes that define the specialized behavior we want.

Obviously we don't want the frame to just call println, so we wrote some code to draw something inside the frame:

        import java.awt.*;

        public class CustomFrame extends Frame {
           public void paint(Graphics g) {
                g.drawString("Hello world!", 50, 50);
                g.setColor(Color.YELLOW);
                g.fillRect(50, 100, 25, 25);
            }
        }
I then extended this example by overriding a method called mouseDown so that it would draw a blue circle whenever the user clicks the mouse. An interesting property of that code is that the blue circles go away if the frame is redrawn because we didn't make it part of our paint method. I joked that this could be thought of as a bug or a feature depending upon your point of view. We also overrode the method mouseDrag to have the program do an etch-a-sketch type operation if the user drags the mouse around.

I also pointed out that it is unfortunate that we don't have more time to explore using inheritance with frameworks like Java's AWT and Swing libraries. In spring of 2007 I taught a course CSE190L that explored this in great detail. The course web page has lecture notes and assignments in case you want to pursue this on your own. I also highly recommend the textbook we used, Core Java.


Stuart Reges
Last modified: Fri Mar 2 17:53:20 PST 2018