CSE143 Notes for Friday, 10/1/10

I spent a few minutes reviewing some basic terminology of object-oriented programming. I mentioned that the following is a good summary of the basic object idea:

An object encapsulates state and behavior.
The terms state and behavior are the technical terms we use, but they are the same ideas people had mentioned. For example, for a radio, the states would include on/off, volume setting, station setting, am/fm and so on. The behavior is that it plays music and that it allows us to change these settings. In programming, state usually means variables (data) and behavior usually means methods.

I then mentioned an idea that I got from Arthur Riel who has written a book on object-oriented design heuristics. Following Riel's advice, I asked everyone in the audience who knows how to use a radio to raise their hand. All the hands went up. Then I asked people to raise their hand if they know how to build a radio from low-level electronic parts. Fewer than 5% of the class raised their hand. This is an important distinction to understand. We all know how to use a radio, but only some of us know how to build one.

In programming, we refer to this as the "client" view and the "implementation" or "implementor" view. Another way of thinking of this is that the external/client view is the "what" part (knowing what the object does) and the internal/implementation view is the "how" part (knowing how the object does it). This is going to be an important concept to understand as we progress through cse143. One of the things that makes this more confusing in cse143 is that we will ask you to switch back and forth between these two views, implementing an object at the same time that you think about how it will be manipulated from the outside by a client.

I mentioned that I will be using Java's ArrayList class as a kind of software cadaver that we will dissect as the quarter progresses. ArrayList is one of the most commonly used classes in the Java Class Libraries. It will take us a while to understand it because of its complexity. To simplify things, we will study something that I call ArrayIntList, which is a variation of ArrayList for storing int values. Even this simplification won't be enough to allow us to understand the class right away, so my plan is to develop the class through several stages, adding functionality at each stage.

I said that we'd need some client code. I suggested that we have something very simple that creates two lists, adds some values to each and prints them. Suppose you're going to write a class like this (where I'm using pseudocode in comments to indicate what I plan to do):

        public class ArrayIntListClient {
            public static void main(String[] args) {
                // construct ArrayIntList list1
                // construct ArrayIntList list2
                // add 1, 82, 97 to list1
                // add 7, -8 to list2
                // print list1
                // print list2
            }
        }
We are going to implement the ArrayIntList as an unfilled array, so we need two variables. We need an array and we need a size variable. Remember that there is a difference between the capacity of the list (the length of the array) and the current size. Our plan is to start out with an empty list initially and to fill it up as the client requests us to add values.

If we're going to have two ArrayIntLists, as in the pseudocode above, then we'd need four variables (two arrays and two sizes). Obviously this isn't a good solution. This is where the ArrayIntList class comes in. Instead of declaring two arrays and two sizes, we can include those variables inside the ArrayIntList class:

        public class ArrayIntList {
            int[] elementData = new int[100];
            int size = 0;
        }
The two variables (the array called elementData and the variable called size) become the data fields or instance variables of the class. These variables become part of what we call the "state" of the object. Once the object is constructed, these variables have a permanence to them that local variables don't have. They stay around indefinitely. They are the "innards" that allow this object to do what it has to do.

So we avoid having four different variables (two arrays and two sizes) by having two different objects (each with its own array and its own size). They will need to be constructed, which means the first two lines in our client code will become calls on "new":

        public class ArrayIntListClient {
            public static void main(String[] args) {
                ArrayIntList list1 = new ArrayIntList();
                ArrayIntList list2 = new ArrayIntList();
                // add 1, 82, 97 to list1
                // add 7, -8 to list2
                // print list1
                // print list2
            }
        }
Then we turned to the idea of adding values to the list. We could directly manipulate the two fields inside of each list object. For example, to add the values 1, 82, 97, to list1, we could set each array element and set the size:

        public class ArrayIntListClient {
            public static void main(String[] args) {
                ArrayIntList list1 = new ArrayIntList();
                ArrayIntList list2 = new ArrayIntList();
                // add 1, 82, 97 to list1
                list1.elementData[0] = 1;
                list1.elementData[1] = 82;
                list1.elementData[2] = 97;
                list1.size = 3;
                // add 7, -8 to list2
                // print list1
                // print list2
            }
        }
But this isn't a very object-oriented approach. We don't want to make the client have to deal with low-level details of the class. Instead, it makes sense to introduce an add method that we can call from the client code:

        public class ArrayIntListClient {
            public static void main(String[] args) {
                ArrayIntList list1 = new ArrayIntList();
                ArrayIntList list2 = new ArrayIntList();
                list1.add(1);
                list1.add(82);
                list1.add(97);
                list2.add(7);
                list2.add(-8);
                // print list1
                // print list2
            }
        }
Notice that each time we call add, we indicate which list we want to add values to (list1 for the first three calls on add, list2 for the next two calls). So we want to include an add method in our ArrayIntList class.

The section handout indicated that the following lines of code could be executed to add a value to the end of an unfilled array:

        elementData[size] = value;
        size++;
We can put this inside a method called "add". What would it's parameters be? Obviously it would need to know the value to be appended to the list. But how does the method get access to the array called elementData and the variable called size? The answer is that we are going to write an instance method which has something known as an implicit parameter. In cse142 we mostly wrote static methods, as in:

        public static void add(int value) {
            // defines a static method add
            ...
        }
By removing the word static from the header, we turn this into an instance method:

        public void add(int value) {
            // defines an instance method add
            ...
        }
Instance methods require the dot notation when you call them. With a static method you could say:

        add(17);  // call on static method
With an instance method, we have to mention which list we want to add a value to, as in:

        list1.add(1);  // calling instance method on list1
        list2.add(7);  // calling instance method on list2
The variable that appears before the dot is known as the implicit parameter. In the first call above, list1 is the implicit parameter. It's as if someone were to shout, "Hey, list1! I'm talking to you. I want you to execute your add method with a value of 1." In the second call, list2 is the implicit parameter ("Hey, list2! I'm talking to you. Execute your add method with a value of 7").

After adding this method to the ArrayIntList class it looked like this:

        public class ArrayIntList {
            int[] elementData = new int[100];
            int size = 0;

            public void add(int value) {
                elementData[size] = value;
                size++;
            }
        }
There are two key ideas here. First, each instance of the ArrayIntList class has its own fields called elementData and size. Second, when we call an instance method like add, there is an implicit parameter (a particular object that we are talking to). When we refer to variables like elementData and size inside an instance method, we are saying, "Modify the fields of the object you are talking to."

We then talked about the idea of printing the two lists. As a starting point, I suggested that we print the size of each list after the calls on add:

        public class ArrayIntListClient {
            public static void main(String[] args) {
                ArrayIntList list1 = new ArrayIntList();
                ArrayIntList list2 = new ArrayIntList();
                list1.add(1);
                list1.add(82);
                list1.add(97);
                list2.add(7);
                list2.add(-8);

                System.out.println(list1.size);
                System.out.println(list2.size);
            }
        }
As you would expect, the program printed the values 3 and 2, which is a good sign. So how do we print the contents of the lists? I suggested that we use the print method like the one discussed in section:

        public static void print(int[] list) {
            if (list.length == 0) {
                System.out.println("[]");
            } else {
                System.out.print("[" + list[0]);
                for (int i = 1; i < list.length; i++) {
                    System.out.print(", " + list[i]);
                }
                System.out.println("]");
            }
        }
We had to make several modifications to turn this into an instance method. We removed the word "static" because this is going to be an instance method. We also removed the parameter "list". Instead, we changed "list" to "elementData" so that we will be looking at the array stored inside of whatever object we are printing. We also had to change "list.length" to "size" because we only want to print values up to the current size (not up to the capacity).

        public void print() {
            if (size == 0) {
                System.out.println("[]");
            } else {
                System.out.print("[" + elementData[0]);
                for (int i = 1; i < size; i++)
                    System.out.print(", " + elementData[i]);
                System.out.println("]");
            }
        }
We then modified our client code to include calls on this print method for each list:

        public class ArrayIntListClient {
            public static void main(String[] args) {
                ArrayIntList list1 = new ArrayIntList();
                ArrayIntList list2 = new ArrayIntList();
                list1.add(1);
                list1.add(82);
                list1.add(97);
                list2.add(7);
                list2.add(-8);
                list1.print();
                list2.print();
                System.out.println(list1.size);
                System.out.println(list2.size);
            }
        }
This worked fairly well. The print commands produced these lines of output:

        [1, 82, 97]
        [7, -8]
I then said let's just see what happens when we include a simple println statement for each list. So at the end of the client code, we added these two lines of code:

        System.out.println("list1 = " + list1);
        System.out.println("list2 = " + list2);
This did not produce good output. It produced a weird output that included the name of the class and an "@" and a hexadecimal number. Someone then suggested the idea of a toString method. We discussed the fact that this print method isn't very flexible. It always sends its output to System.out. What if you are writing a GUI (Graphical User Interface) and want the output to go to some particular part of the screen? What if you are writing to a file? What if you want to print several lists on a single line of output? We couldn't do any of these things with this print method.

So we then rewrote the print method to have it return a String. Java has a standard name for a method that converts an object into text form: toString, so we also changed its name:

        public String toString() {
            if (size == 0) {
                return "[]";
            } else {
                String result = "[" + elementData[0];
                for (int i = 1; i < size; i++)
                    result += ", " + elementData[i];
                result += "]";
                return result;
            }
        }
We then had to change our client code to print the String returned by this method. At this point, our client code looked like this:

        public class ArrayIntListClient {
            public static void main(String[] args) {
                ArrayIntList list1 = new ArrayIntList();
                ArrayIntList list2 = new ArrayIntList();
                list1.add(1);
                list1.add(82);
                list1.add(97);
                list2.add(7);
                list2.add(-8);
                System.out.println(list1.toString());
                System.out.println(list2.toString());
                System.out.println(list1.size);
                System.out.println(list2.size);
                System.out.println("list1 = " + list1);
                System.out.println("list2 = " + list2);
            }
        }
The first two printlns were calling our toString method and showed the list contents, as we would expect. The next two lines reported the sizes of the lists. The final two lines didn't work before, but suddenly the two println calls at the end of the program started producing good output. That's because the toString method in Java has special properties. You can call it explicitly, as in the first two calls, but you can also leave off the call, as in the last two printlns, in which case Java calls toString for you (an implicit call).

I said that we would continue our discussion of the ArrayIntList class in our next lecture.


Stuart Reges
Last modified: Mon Oct 4 10:34:11 PDT 2010