CSE143 Notes for Monday, 3/3/14

I squeezed three different topics into one lecture. I gave abbreviated versions of the first two topics. 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 reminded people that in terms of grading, we require that all preconditions be commented along with the fact that an exception is thrown. We require that you mention exactly what type of exception is thrown, as in the comment above.

We also discussed a "bulk add" method called addAll. It may seem a little odd to have one ArrayIntList dealing with another ArrayIntList, but this actually happens fairly often and you will have to practice it in your next homework assignment. The idea is that "this" ArrayIntList is supposed to add all of the values from the "other" ArrayIntList. So its header looks like this:

        public void addAll(ArrayIntList other) {
            ...
        }
We can call the appending add method to add values to the list. We just have to figure out how to write a loop that indicates what values to add. I first wrote the method this way:

        public void addAll(ArrayIntList other) {
            for (int i = 0; i < other.size(); i++)
                add(other.get(i));
        }
This approach works. Here were are using the size and get methods to access the values in the other list. But it also works to directly refer to the private fields themselves, as in:

        public void addAll(ArrayIntList other) {
            for (int i = 0; i < other.size; i++)
                add(other.elementData[i]);
        }
It may seem odd that one ArrayIntList object can access the private fields of another, but that's how it works in Java. Private means private to the class. That doesn't match our own human intuitions about privacy. Often it can be more efficient to directly access private fields and methods and sometimes a problem can't be solved at all without such access.

I mentioned that I also included bulk removeAll method along with a method called clear that empties the list.

Then I said I wanted to discuss a concept that we will be exploring a lot as we look at other collection classes. First I wrote some code to construct an ArrayIntList and to fill it up with values:

        public class ArrayIntListExample {
            public static void main(String[] args) {
        	ArrayIntList list = new ArrayIntList();
        	list.add(12);
        	list.add(3);
        	list.add(3);
        	list.add(72);
        	list.add(42);
        	list.add(3);
        	list.add(-19);
        	System.out.println("list = " + list);
            }
        }
When we ran it, it produced the following output:

        list = [12, 3, 3, 72, 42, 3, -19]
I then asked how we could find the sum of the values in the list. Someone said we should use a for loop and that we should use the size method of ArrayIntList to figure out how many times to loop and that we should use the get method of ArrayIntList to get the individual values:

	int sum = 0;
	for (int i = 0; i < list.size(); i++) {
	    sum += list.get(i);
	}
	System.out.println("sum = " + sum);
When we ran this new version of the program, it produced the following output:

        list = [12, 3, 3, 72, 42, 3, -19]
        sum = 116
This is often a reasonable way to manipulate a list, but I said that I wanted to explore a different approach using what is known as an iterator. The code we've written to sum the list works, but it relies on the "get" method being able to quickly access any element of the structure. This property is known as random access. We say that arrays and the ArrayList and ArrayIntList classes that are built on top of them are random access structures because we can quickly access any element of the structure. If you knew that for the rest of your life, you'd always be working with arrays, then you'd have little use for iterators. You'd just call the get method because with arrays you get fast random access.

But many of the other data structures we will be looking at don't have this kind of quick access. Think of how a DVD works, quickly jumping to any scene, or how a CD works, quickly jumping to any track, versus how a VHS tape works, requiring you to fast forward through every scene until you get to the one you want. For those other structures we will study, iterators will make a lot more sense. So iterators will seem a bit silly when tied to an array-based structure, but we'll eventually see much more interesting examples of iterators.

In general, we think of an iterator as having three basic operations:

Sun adopted the convention early on that the second and third steps would be combined into one operation known as "next" that does two different things: it returns the next value and it advances the iterator to the next value. So in Java there are two fundamental operations:

We wrote a loop to find the product of the values in the list by using an iterator:

	Iterator<Integer> i =  list.iterator();
	int product = 1;
	while (i.hasNext()) {
	    int next = i.next();
	    product = product * next;
	}
	System.out.println("product = " + product);
This involves a new kind of object of type ArrayIntListIterator. Once we have our iterator, we can use it to go through the values in the list one at a time.

To make this even more clear, we added a println inside the loop:

	Iterator<Integer> i =  list.iterator();
	int product = 1;
	while (i.hasNext()) {
	    int next = i.next();
     	    System.out.println("product = " + product + ", next = " + next);
	    product = product * next;
	}
	System.out.println("product = " + product);
This loop produced the following output:

        product = 1, next = 12
        product = 12, next = 3
        product = 36, next = 3
        product = 108, next = 72
        product = 7776, next = 42
        product = 326592, next = 3
        product = 979776, next = -19
        product = -18615744
I also briefly mentioned that iterators often support a method called remove that allows you to remove the value that you most recently get from a call on next. For example, this variation of the code prints each value and removes any occurrences of the value 3:

	Iterator<Integer> i =  list.iterator();
	int product = 1;
	while (i.hasNext()) {
	    int next = i.next();
     	    System.out.println("product = " + product + ", next = " + next);
	    product = product * next;
            if (next == 3)
                i.remove();
	}
	System.out.println("product = " + product);
This code examines each value in the list and removes all the occurrences 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 {
            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. 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 I discussed an optional example that involves inheritance to define a Graphical User Interface using the AWT library. Because it was an optional example, I will not provide detailed notes about it. The code is included under the calendar.

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: Wed Mar 12 09:14:01 PDT 2014