CSE143 Notes for Monday, 10/10/05

I started by pointing out how these notes can be accessed from the class web page through the calendar. Then I showed that under "useful links", the first link is to the Java api documentation. This is an extremely useful resource that students should get used to accessing. You have to keep in mind that the class libraries are vast, so don't expect to understand most of what you come across. Using the list in the lower left part of the page you can access a specific class to find out what methods it includes. For example, you could look at the String class to see what you can and can't do with a String object.

I used the String class to point out several aspects of the documentation. First, above the class name you will see the name of the package it is part of. In the case of String this is the "java.lang" package which is automatically imported for you. For other packages you'll have to include an import declaration.

I then pointed out a few different methods. For example, there is a "charOf" method that can be used to extract individual characters from a String. I also pointed out that there are a series of "valueOf" methods in the String class. These are static methods. You have to pay close attention to this documentation to see when something is a static method and when it's a normal method (what might be called a "dynamic" or "instance" method, which is the default in Java).

I spent a few minutes trying to explain static versus non-static elements of a class, but I also pointed out that this is a confusing point that many people still don't quite understand when they leave cse143. Nonstatic methods are associated with the instances of the class. Static methods are associated with the class itself. You can think of static elements as being "shared" or "just one for the entire class."

I gave two analogies. For a bank account, there are certain things that are associated with the individual accounts, like the current balance. If the balance were static, then we'd all be sharing the same bank balance (and I'd be very happy if I managed to get in line behind Bill Gates to withdraw whatever money he deposits). Obviously bank balances are not static, we need a different one for each account. But something like the fee charged for a bounced check is something that can be shared, so it can be static. Each account doesn't need its own copy of the bounced check fee. The other analogy I gave was to Honda Accords. If you want to turn the steering wheel, you need an actual Honda Accord to steer. Each Accord has its own steering wheel. But if I wanted to know how many cylinders a standard Accord comes with, I can just call the dealership and ask, because this is a shared value.

I pointed out that for nonstatic methods like "charOf", you need an actual String object to talk to. It wouldn't make sense to ask the String class for "charOf". You have to talk to a specific String object to ask about its characters. But the "valueOf" methods are static, which means we call them by talking to the class. We'd refer to String.valueOf. We've seen this before with the Math class. Almost all of the methods in the Math class are static. So you ask for Math.random or Math.max or Math.sin. You don't construct a Math object. My main point was that you have to pay attention to whether or not a method is declared as static so that you'll know how to call it.

For example, I showed the Character class and pointed out that is has methods like toLowerCase. You have to read the method headers carefully to know exactly how to use it. In the case of toLowerCase, it is listed as a static method. So instead of saying something like:

        letter.toLowerCase()
you'll instead say something like:
        Character.toLowerCase(letter)
Someone asked about where to get the source code for the Java class libraries. I wasn't able to demonstrate it on the computer in the classroom because they don't allow me to access the "c" drive, but I described what to do. If you've installed Java 5 on your home computer, you should go to c:\, open up "Program Files", open up "Java" and open up the folder that has "jdk1.5" in its name (on my computer it is called jdk1.5.0_05"). Inside that folder you'll find a file called src.zip. The name "src" is short for "source". I have unzipped this folder on my machine so that I can access the source code.

Then you have to know something about how to navigate using package information. As an example, I described how I found the source code to Java's binary search method. It is part of the Arrays class (Arrays.binarySearch). So I used the Java api to look at that class description. It indicates that it is part of the java.util package. Package structure is reflected in folder structure. So the "java.util" package is inside a folder called "java" and inside an inner folder called "util". So starting with the source code folder, open java, open util, and you'll find Arrays.java. That's what I did and I was able to see Sun's implementation of all of those methods.

Then we turned our attention to what are known as linked lists. I mentioned that this particular topic is one that students find challenging but this is when people finally realize exactly what we've been talking about when we say that objects are stored as "references". I also pointed out that in other programming languages we use what are called "pointers" to implement linked lists. Mention "linked lists" or "pointers" to people who have studied them and they are likely to wince because they remember how confusing they can be.

Linked lists are composed of individual elements called nodes. Each node is like a Lego building block. It looks unimpressive by itself, but once you put a bunch of them together, it can form an interesting structure.

A basic list node looks like this:

        +------+------+
        | data | next |
        |  18  |  +---+--->
        +------+------+
It's an object with two data fields: one for storing a single item of data and one for storing a reference to the next node in the list. For a list of int values, we'd declare this as follows:

        public class ListNode {
            public int data;
            public ListNode next;
        }
I pointed out that this isn't a nicely encapsulated object because of the public data fields. I said that I'd discuss this in Wednesday's lecture (why this is okay to do). I also pointed out that this is a recursive data structure (a class that is defined in terms of itself in that the class is called ListNode and it has a data field of type ListNode).

Then we wrote some code that would build up the list (3, 7, 12). Obviously we're going to need three nodes that are linked together. With linked lists, if you have a reference to the front of the list, then you can get to anything in the list. So we'll usually have a single variable of type ListNode that refers to (or points to) the front of the list. So we began with this declaration:

        ListNode list;
The variable "list" is not itself a node. It's a variable that is capable of referring to a node. So we'd draw it something like this:

         +---+
    list | ? |
         +---+
where we understand that the "?" is going to be replaced with a reference to a node. So this box does not have a "data" field or a "next" field. It's a box where we can store a reference to such an object.

We don't have an actual node until we call new:

        list = new ListNode();
This constructs a new node and tells Java to have the variable "list" refer to it:

                    +------+------+
         +---+      | data | next |
    list | +-+--->  |      |      |
         +---+      +------+------+
What do we want to do with this node? We want to store 3 in its data field (list.data) and we want its next field to point to a new node:

        list.data = 3;
        list.next = new ListNode();
which leads us to this situation:

                    +------+------+      +------+------+
         +---+      | data | next |      | data | next |
    list | +-+--->  |   3  |   +--+--->  |      |      |
         +---+      +------+------+      +------+------+
When you program linked lists, you have to be careful to keep track of what you're talking about. The variable "list" stores a reference to the first node. We can get inside that node with the dot notation (list.data and list.next). So "list.next" is the way to refer to the "next" box of the first node. We wrote code to assign it to refer to a new node, which is why "list.next" is pointing at this second node.

Now we want to assign the second node's data field (list.next.data) to the value 7 and assign the second node's next field to refer to a third node:

        list.next.data = 7;
        list.next.next = new ListNode();
which leads us to this situation:

                    +------+------+      +------+------+      +------+------+
         +---+      | data | next |      | data | next |      | data | next |
    list | +-+--->  |   3  |   +--+--->  |   7  |   +--+--->  |      |      |
         +---+      +------+------+      +------+------+      +------+------+
I again repeated the idea of paying close attention to list versus list.next versus list.next.next and remember which box each of those coincides with:

                    +------+------+      +------+------+      +------+------+
         +---+      | data | next |      | data | next |      | data | next |
    list | +-+--->  |   3  |   +--+--->  |   7  |   +--+--->  |      |      |
         +---+      +------+------+      +------+------+      +------+------+
           |                   |                    |
           |                   |                    |
          list             list.next          list.next.next
Finally, we want to set the data field of this third node to 12 (list.next.next.data) and we want to set its next field to null. The keyword "null" is a Java word that means "no object". This provides a "terminator" for the linked list (a special value that indicates that we are at the end of the list). So we'd execute these statements:

        list.next.next.data = 12;
        list.next.next.next = null;
which leaves us in this situation:

                    +------+------+      +------+------+      +------+------+
         +---+      | data | next |      | data | next |      | data | next |
    list | +-+--->  |   3  |   +--+--->  |   7  |   +--+--->  |  12  |   /  |
         +---+      +------+------+      +------+------+      +------+------+
We draw a diagonal line through the last "next" field as a way to indicate that it's value is null.

Obviously this is a very tedious way to manipulate a list. It's much better to write code that involves loops to manipulate lists. But it takes a while to get used to this idea, so we're first going to practice how to do some raw list operations without a loop. In section we will go over 10 different exercises that involve list operations. Each will have a "before" picture and an "after" picture. The challenge is to write code that gets you from the one state to the other state.

As an example, suppose that you have two variables of type ListNode called p and q and that you have the following situation:

                    +------+------+      +------+------+
         +---+      | data | next |      | data | next |
       p | +-+--->  |   2  |   +--+--->  |   4  |   /  |
         +---+      +------+------+      +------+------+

                    +------+------+      +------+------+
         +---+      | data | next |      | data | next |
       q | +-+--->  |   3  |   +--+--->  |   9  |   /  |
         +---+      +------+------+      +------+------+
and you want to get to this situation:

                    +------+------+      +------+------+      +------+------+
         +---+      | data | next |      | data | next |      | data | next |
       p | +-+--->  |   2  |   +--+--->  |   4  |   +--+--->  |   3  |   /  |
         +---+      +------+------+      +------+------+      +------+------+

                    +------+------+
         +---+      | data | next |
       q | +-+--->  |   9  |   /  |
         +---+      +------+------+
How do we do it? I started by asking people how many variables of type ListNode we have. I got various answers. Some people said two (probably thinking of p and q). Other people said four (probably thinking of p, q and the two non-null links). But in fact, there are six different variables of type ListNode. I numbered each one:

                              2                    3
           1        +------+------+      +------+------+
         +---+      | data | next |      | data | next |
       p | +-+--->  |   2  |   +--+--->  |   4  |   /  |
         +---+      +------+------+      +------+------+

                               5                    6
           4        +------+------+      +------+------+
         +---+      | data | next |      | data | next |
       q | +-+--->  |   3  |   +--+--->  |   9  |   /  |
         +---+      +------+------+      +------+------+
Then I asked which of these variables has to change in value. The answer is that the boxes numbered 3, 4 and 5 have to be changed. If we change them appropriately, we'll be done. But we have to be careful of how we do so. Order can be important. For example, suppose we were going to start by changing box 4. In the final situation, it's supposed to point at the node with 9 in it. But if we started with that change, then what would happen to the node with 3 in it? We'd lose track of it. This is potentially a problem.

I mentioned that a good analogy for this is to think of the nodes as helium balloons and to think of the arrows as strings attached to the balloons. If we let go of one of the strings, the balloon floats away. In technical terms, we'd say that the object is no longer reachable. Java has a "garbage collector" that wanders around looking for such objects and it reclaims the space to use in constructing other objects.

Of the three values we have to change to solve this problem, the one that is safe to change is box 3 because it's currently null. So we begin by setting it to point to the node with 3 in it:

        p.next.next = q;
Now that we've used the value of box 4 to reset box 3, we can reset box 4. It's supposed to point to the node that has 9 in it. We can do this by "leap frogging" over the current node it's pointing to:

        q = q.next;
Now we just have to reset box 5. But we can no longer refer to box 5 as q.next because we've changed q. Now we have to refer to it this way:

        p.next.next.next = null;
Putting these three lines together, we see the code that is needed to get from the initial state to the final state:

        p.next.next = q;
        q = q.next;
        p.next.next.next = null;
Obviously this can be very confusing. It is essential that you draw pictures to keep track of what is pointing where and what is going on when this code executes. It's the only way to master linked list code. We'll practice these small problems in section so that in lecture on Wednesday we can turn to the question of how to use loops to do more generalized processing of linked lists.


Stuart Reges
Last modified: Tue Oct 11 14:25:01 PDT 2005