CSE143 Notes for Monday, 4/7/08

I began by talking about the idea of an iterator. Iterators are appropriate when you want to examine every value in a structure from first to last. For example, suppose that you want to print each value in an ArrayIntList one per line. You could say:

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
This code works, but it relies on a "get" method that can quickly access any element of the array. This is known as random access. 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, 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:

The loop we wrote for printing the values in the list can be rewritten with an iterator as follows:

        ArrayIntListIterator i = list.iterator();
        while (i.hasNext()) {
            System.out.println(i.next());
        }
This involves a new kind of object of type ArrayIntListIterator. We get one by calling a special method in the list class. Once we have our iterator, we can use it to go through the values in the list one at a time.

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:

        ArrayIntListIterator i = list.iterator();
        while (i.hasNext()) {
            int n = i.next();
            System.out.println(n);
            if (n == 3) {
                i.remove();
            }
        }
This code examines each value in the list and removes all the occurrences of 3. We also looked at "tricky" cases for remove. What could cause it to fail? Someone mentioned that removing something twice might be a problem, as in:

        while (i.hasNext()) {
            int n = i.next();
            if (n == 3) {
                i.remove();
                i.remove();
            }
        }
It would also be a problem to try removing before next has been called.

I then spent some time talking about the built-in ArrayList class. Remember that we're studying the ArrayIntList class as a way to understand the built-in ArrayList class. I first had to discuss relatively new feature of Java known as "generics." We know that for arrays, it is possible to construct arrays that store different types of data:

But arrays are a special case in Java. If they didn't already exist, we couldn't easily add them to Java. Instead, Java now allows you to declare generic classes and generic interfaces. For example, the ArrayList class is similar to an array. Instead of declaring ordinary ArrayList objects, we declare ArrayList<E> where E is some type (think of E as being short for "Element type"). The "E" is a type parameter that can be filled in with the name of any class.

For example, suppose, we want an ArrayList of Strings. We describe the type as:

        ArrayList<String>
When we construct an ArrayIntList, we say:
        ArrayIntList lst = new ArrayIntList();
Imagine replacing both occurrences of "ArrayIntList" with "ArrayList<String>" and you'll see how to construct an ArrayList<String>:
        ArrayList<String> lst = new ArrayList<String>();
And in the same way that you would declare a method header for manipulating an ArrayIntList object:

        public void doSomethingCool(ArrayIntList lst) {
            ...
        }
You can use ArrayList<String> in place of ArrayIntList to declare a method that takes an ArrayList<String> as a parameter:

        public void doSomethingCool(ArrayList<String> list) {
            ...
        }
It can even be used as a return type if you want to have the method return an ArrayList:

        public ArrayList<String> doSomethingCool(ArrayList<String> list) {
            ...
        }
Once you have declared an ArrayList<String>, you can use manipulate it with the kinds of calls we have made on our ArrayIntList but using Strings instead of ints:

        ArrayList<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("there");
        list.add(0, "fun");
        System.out.println(list);
which produces this output:

        [fun, hello, there]
All of the methods we have seen with ArrayIntList are defined for ArrayList: the appending add, add at an index, remove, size, get, etc. So we could write the following loop to print each String from an ArrayList<String>:

        for (int i = 0; i < lst.size(); i++) {
            System.out.println(lst.get(i));
        }
I then spent a little time discussing the issue of primitive data versus objects. Even though we can construct an ArrayList<E> for any class E, we can't construct an ArrayList<int> because int is a primitive type, not a class. To get around this problem, Java has a set of classes that are known as "wrapper" classes that "wrap up" primitive values like ints to make them an object. It's very much like taking a candy and putting a wrapper around it. For the case of ints, there is a class known as Integer that can be used to store an individual int. Each Integer object has a single data field: the int that it wrapped up inside.

Java 5 also has quite a bit of support that makes a lot of this invisible to programmers. If you want to put int values into an ArrayList, you have to remember to use the type ArrayList<Integer> rather than ArrayList<int>, but otherwise Java does a lot of things for you. For example, you can construct such a list and add simple int values to it:

        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(18);
        list.add(34);
In the two calls on add, we are passing simple ints as arguments to something that really requires an Integer. This is okay because Java will automatically "box" the ints for us (i.e., wrap them up in Integer objects). We can also refer to elements of this list and treat them as simple ints, as in:

        int product = list.get(0) * list.get(1);
The calls on list.get return references to Integer objects and normally you wouldn't be allowed to multiply two objects together. In this case Java automatically "unboxes" the values for you, unwrapping the Integer objects and giving you the ints that are contained inside.

Every primitive type has a corresponding wrapper class: Integer for int, Double for double, Character for char, Boolean for boolean, and so on.

Then I mentioned that I hoped people are aware of the array initializer syntax where you can use curly braces to specify a set of values to use for initializing an array:

        int[] data = {8, 27, 93, 4, 5, 15, 206};
This is a great way to define data to use for a testing program. I asked people how we'd find the product of this list and people suggested the standard approach that uses an int to index the array:

        int product = 1;
        for (int i = 0; i < data.length; i++) {
            product *= data[i];
        }
This approach works, but there is a simpler way to do this. If all you want to do is to iterate over the values of an array one at a time, you can use what is called a for-each loop:

        int product = 1;
        for (int n : data) {
            product *= n;
        }
We generally read the for loop header as, "For each int n in data...". The choice of "n" is arbitrary. It defines a local variable for the loop. I could just as easily have called it "x" or "foo" or "value". Notice that in the for-each loop, I don't have to use any bracket notation. Instead, each time through the loop Java sets the variable n to the next value from the array. I also don't need to test for the length of the array. Java does that for you when you use a for-each loop.

There are some limitations of for-each loops. You can't use them to change the contents of the list. If you assign a value the variable n, you are just changing a local variable inside the loop. It has no effect on the array itself.

As with arrays, we can use a for-each loop for ArrayLists, so we could say:

        String[] data2 = {"four", "score", "and", "seven", "years", "ago"};
        ArrayList<String> lst = new ArrayList<String>();
        for (String s : data2) {
            lst.add(s);
        }
        System.out.println(lst);
which produces this output:

        [four, score, and, seven, years, ago]

Stuart Reges
Last modified: Mon Apr 7 16:02:47 PDT 2008