CSE143 Notes for Friday, 1/6/06

I started by pointing out that in the first two assignments we will be reviewing arrays and classes. This should give everyone some time to remember how these things work and to fill in any gaps in your knowledge. It's important to understand typical array processing and the basic use of classes. It's particularly important to understand the IntList class. Chapters 7 (arrays) and 8 (defining classes) from the cse142 class will be particularly helpful for review (available under "useful links" from the class page).

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 10% 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. This is going to be an important concept to understand as we progress through cse143.

I then turned to the solution key for the section problems from Tuesday. You were asked to consider two variables that were used to store a list of integers (an array for the integers that had a high capacity and a separate integer variable that kept track of the current number of elements in use in the array). There were five coding problems to manipulate the pair of variables. I pointed out that this combination (the two variables and the five pieces of code) could be turned into a class.

I started by thinking about it from the point of view of a client program. Suppose you're going to write a class like this (where I'm using pseudocode descriptions of what I want to do):

        public class ClientOfIntList {
            public static void main(String[] args) {
                make an IntList list1
                make an IntList list2
                add 3 to list1
                add 8 to list1
                add 37 to list2
            }
        }
We saw that we need two different variables for an IntList (an array and a size). If we're going to have two IntList's, as in the code above, then we're going to need four variables (two arrays and two sizes). The two variables (the array called elementData and the variable called size) become the data fields or instance variables of the class. By defining them at the outer level of the class:

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

            ...
        }
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. These variables are like the tape and reels inside a cassette. 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 something like this:

        IntList list1 = new IntList(??);
        IntList list2 = new IntList(??);
        ...
I asked what we might want to include inside the parentheses and someone mentioned that we might want to specify the capacity. For example, maybe list1 can be fairly small with a capacity of just 45, but perhaps for list2 we want a much higher capacity like 3495. So we might want to specify this when we construct the lists:

        IntList list1 = new IntList(45);
        IntList list2 = new IntList(3495);
        ...
It's important to keep in mind that the call on "new" is a call on an actual method of the class called a constructor. Currently we are initializing the variables in a clumsy way:

        public class IntList {
            int[] elementData = new int[100];
            int size = 0;
It is considered better style to have a constructor method. Plus, this code would always set up an array with a capacity of 100 and we're trying to give the client the option to construct a list with a different capacity. So we introduced this constructor:

    public IntList(int capacity) {
        this.elementData = new int[capacity];
        this.size = 0;
    }
Constructors are special methods that are only called in conjunction with "new". They have the same name as the class and have no return type. Their only purpose is to initialize the data fields of the object. Notice that we are using the "this." notation. That is because the variables we are referring to are not local variables in the method. They are data fields of the object. The "this." notation is optional, but it makes it very clear exactly what is going on.

We then took the first of the five code segments and turned it into a method. We had these two lines of code to append a value to the end of a list:

        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. What about the array and size variables? This is another place where we used the "this" notation. The keyword "this" refers to the "implicit parameter," the current object. We're going to want to take this pseudocode:

        add 3 to list1
        add 8 to list1
        add 37 to list2
and turn it into something like this:

        list1.add(3);
        list1.add(8);
        list2.add(37);
In each case, we begin by specifying the object we want to deal with (either list1 or list2) and then what we want to do (add a value). The keyword "this" will refer to list1 in the first two calls and will refer to list2 in the third call. Thus, if we write the method as follows:

        public void add(int value) {
            this.elementData[this.size] = value;
            this.size++;
        }
That way for the first two calls the when "this" refers to "list1", we are changing list1.elementData and list1.size (the two data fields of the first list object). In the third call "this" refers to "list2" and we end up changing list2.elementData and list2.size (the two data fields of the second list object).

I then talked about the idea of encapsulation. As it stands, because of the way that the variables are declared, someone outside the class could directly manipulate the variables, as in:

        IntList list1 = new IntList();
        list1.add(3);
        list1.add(8);
        list.size = -37;
        ...
This is not a good idea. It is called "breaking encapsulation." It is dangerous to allow someone outside the class to reach in and directly manipulate a variable. In this case, we'd want to have size equal to 2 after two values have been added to the list. By allowing the client to directly access the variable, the client can damage the internal state of the object.

When it comes to sensitive electronics, you'll find that there is often a warning that if you open up the object (break encapsulation), then you void your warranty. In the same way, we don't wan t to let people open up our objects because otherwise we can't ensure that they are in a good state.

So we add what is known as an "access modifier" to the variable declaration saying that it has "private" access:

        public class IntList {
            private int[] elementData;
            private int size;

            ...
        }
Then if the client tries to directly change the value of the data field, the compiler will generate an error message. Of course, now the client has no way of finding out the size. So we add a method called "size" that will allow the client to get the value: as:

    public int size() {
        return this.size;
    }
It might seem like we haven't accomplished anything by doing this, but we actually have. For example, with direct access to the size variable, a client could reset size to an inappropriate value. That won't happen now. By making the data fields private, we can guarantee the internal integrity of our IntList objects. This is one of the primary advantages of encapsulation.


Stuart Reges
Last modified: Sun Jan 8 17:38:44 PST 2006