CSE143 Notes for Wednesday, 4/2/08

I discussed the fact 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. 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);
        System.out.println(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 rewrite the print method to have it return a String. At first, I kept the old name "print":

        public String print() {
            if (size == 0) {
                return "[]";
            } else {
                String result = "[" + elementData[0];
                for (int i = 1; i < size; i++)
                    result += ", " + elementData[i];
                result += "]";
                return result;
            }
        }
We had to change the client code to include the System.out.println:

        System.out.println(list1.print());
        System.out.println(list2.print());
This worked fine. 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.print());
                System.out.println(list2.print());
                System.out.println(list1.size);
                System.out.println(list2.size);
                System.out.println(list1);
                System.out.println(list2);
            }
        }
The first two println's were calling our print method and showed the list contents. The next two lines reported the sizes of the lists. The final two lines produced the weird output with an "@" in the middle.

Someone pointed out that Java has a standard name for a method that converts an object into text form: toString. So we went back to the print method and 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;
            }
        }
When we did this, the program produced a different output. 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 call above, but you can also leave off the call, as in the other two printlns:

        System.out.println(list1);
        System.out.println(list2);
in which case Java calls toString for you (an implicit call). Similarly, you can use String concatenation and Java will implicitly call your toString method, as in:

        System.out.println("list1 = " + list1);
        System.out.println("list2 = " + list2);
which produces the following output:

        list1 = [1, 82, 97]
        list2 = [7, -8]
I then turned to the idea of encapsulation, which we discussed briefly on Wednesday. When you buy a radio or other appliance at Best Buy that you'll find that all of the electronics are inside of a plastic or metal case. We would say that the electronics are encapsulated inside this case. You can't see them or touch them from the outside. In fact, if you flip the device over, you're likely to find a metal plate with screws that can be removed, but it often comes with a warning along the lines of, "Do not remove. You will void your warranty if you remove this."

Why the warning? Someone said that they don't want you to damage the electronics and that is exactly right. So is there something analogous in the ArrayIntList class we have been writing? What might the client might do to damage the object? Someone said that they could set the size to a negative number:

        list1.size = -384;
Allowing a client to reset the size of the list 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 field. Fortunately, there is a simple fix. We can declare the fields of the class to be private:

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

            ...
        }
When we made this change, the dangerous line of code was now considered an error:

        ArrayIntListClient.java:15: size has private access in ArrayIntList
                        list1.size = -3;
                             ^
        1 error
By saying that the fields are private, we are saying that they cannot be accessed outside the class. This will allow us to protect the integrity of our ArrayIntList objects. In particular, it will give us the power to make sure that our ArrayIntList objects are never in a corrupt state. If a client tries to set the size field to something inappropriate like -3, Java will generate the error message above.

But we also got error messages for the following two lines of code:

        System.out.println(list1.size);
        System.out.println(list2.size);
By saying that the fields are private, we are saying that they cannot be accessed outside the class. This is in general a good thing, but it seems like we want to allow clients to check the size of a list. I said that we'd pick up with that idea in Friday's lecture.


Stuart Reges
Last modified: Wed Apr 2 19:09:14 PDT 2008