CSE143 Notes for Wednesday, 1/18/06

I began by discussing some details of type char. This can be confusing because we also know about type String that can store characters (even just a single character). But these two types are very different. In particular, type char is a primitive type, which means it is in some sense more like type int, while type String is a reference type (each String is an object).

The values of type char are ordered by what is known as a "collating sequence." Think of it as being like a big decoder ring that tells you which character corresponds to which integer. Java's char type follows what is known as ASCII:

        A merican
        S tandard
        C ode for
        I nformation
        I nterchange
There are 128 different ASCII characters. Java's char type is actually much richer than this, using something called "unicode" that is capable of storing many more characters (including non-US symbols, Japanese characters, Chinese characters, etc). But for our purposes, we are limiting ourselves to ASCII characters.

Within this sequence, the alphabetic characters appear sequentially as two blocks. The uppercase letters ('A', 'B', 'C', ..., 'Z') appear together in order and the lowercase letters ('a', 'b', 'c', ..., 'z') also appear together in order. You don't need to know where exactly they appear in the ASCII sequence, although if you're curious, TextPad has a table of these characters that appears in the lower-left corner of the screen when you open TextPad. In Java programs, it is best not to write code that relies on specific ASCII values. Whenever you need to convert one into an int, Java will do it automatically for you.

The assignment writeup gives two specific examples of this. For example, if you ask for:

        ch - 'a'
You are asking for the difference between a character's ASCII value and the ASCII value of a lowercase 'a'. This gives you an offset from 'a'. The assignment writeup also points out that you can go in the other direction with a cast to char:

        (char) ('a' + 8)
This adds 8 to the ASCII value for lowercase 'a' and turns that int into its character equivalent. Those are the kinds of expressions you should be using in the LetterInventory program.

Because char is ordered, you can also compare values of type char, as in:

        if (ch < 'a') {
            ...
        }
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 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)
I used handout #8 to review what we had discussed on Monday, that we have a class called ListNode that has data fields for "data" and "next" and that has several constructors:
        public class ListNode {
            public int data;
            public ListNode next;
        
            public ListNode() {
                this(0, null);
            }
        
            public ListNode(int data) {
                this(data, null);
            }
        
          public ListNode(int data, ListNode next) {
              this.data = data;
              this.next = next;
          }
        }
As with the other classes we've seen, there is one "real" constructor (the one that takes two arguments). The other two use the "this(...)" notation to call the third constructor with default values (0 for the data, null for next).

Then I turned to the question of how to write code that would print out the contents of one of these lists. We have just one variable to work with, so that's clearly where we have to start (the variable "front" or "this.front"). We could use it to move along the list and print things out, but then we would lose the original value of the variable which would mean that we would have lost the list. Instead, we declare a local variable of type ListNode that we will use to access the different data fields of the list:

        ListNode current = front;
This initializes current to point to the same value as front (the first node in the list). We want to have a loop that prints the various values and we want it to keep going as long as there is more data to print. Suppose that the list stores the values (3, 5, 2). Then after executing the statement above, we have the following situation:

                    +------+------+      +------+------+      +------+------+
         +---+      | data | next |      | data | next |      | data | next |
   front | +-+--->  |   3  |   +--+--->  |   5  |   +--+--->  |   2  |   /  |
         +---+      +------+------+      +------+------+      +------+------+
                           ^
         +---+             |
 current | +-+------>------+
         +---+
So how do we structure our loop? We want to keep going while there is more data to print. The variable current will end up referring to each different node in turn. The final node has the value null in its next field, so eventually the variable current will become null and that's when we know we're done. So our basic loop structure will be:

ListNode current = front; while (current != null) { <process next value> } To process a node, we need to print out its value, which we can get from current.data, and we need to move current to the next node over. The position of the next node is stored in current.next, so moving to that next node involves resetting current to current.next:

        ListNode current = front;
        while (current != null) {
            System.out.println(current.data);
            current = current.next;
        }
The first time through this loop, current is referring to the node with the 3 in it. It prints this value and then resets current, which causes current to refer to (or point to) the second node in the list:

                    +------+------+      +------+------+      +------+------+
         +---+      | data | next |      | data | next |      | data | next |
   front | +-+--->  |   3  |   +--+--->  |   5  |   +--+--->  |   2  |   /  |
         +---+      +------+------+      +------+------+      +------+------+
                                                ^
         +---+                                  |
 current | +-+------>------>------>------>------+
         +---+
Some people prefer to visualize this differently. Instead of thinking of the variable current as sitting still while its arrow moves, some people prefer to think of the variable itself moving. So for the initial situation they'd draw this picture:

                    +------+------+      +------+------+      +------+------+
         +---+      | data | next |      | data | next |      | data | next |
   front | +-+--->  |   3  |   +--+--->  |   5  |   +--+--->  |   2  |   /  |
         +---+      +------+------+      +------+------+      +------+------+
                           ^
                           |          
                         +-+-+
                 current | + |
                         +---+
And after executing the statement "current = current.next", we'd have this situation:

                    +------+------+      +------+------+      +------+------+
         +---+      | data | next |      | data | next |      | data | next |
   front | +-+--->  |   3  |   +--+--->  |   5  |   +--+--->  |   2  |   /  |
         +---+      +------+------+      +------+------+      +------+------+
                                                ^
                                                |          
                                              +-+-+
                                      current | + |
                                              +---+
Either way of thinking about this works. Because in this new situation the variable current is not null, we once again go into the loop and print out current.data (which is now 5), and move current along again:

                    +------+------+      +------+------+      +------+------+
         +---+      | data | next |      | data | next |      | data | next |
   front | +-+--->  |   3  |   +--+--->  |   5  |   +--+--->  |   2  |   /  |
         +---+      +------+------+      +------+------+      +------+------+
                                                                     ^
         +---+                                                       |
 current | +-+------>------>------>------>------>------>------>------+
         +---+
Once again current is not null, so we go into the loop a third time and print the value of current.data (2) and reset current. But this time current.next has the value null, so when we reset current we get:

                    +------+------+      +------+------+      +------+------+
         +---+      | data | next |      | data | next |      | data | next |
   front | +-+--->  |   3  |   +--+--->  |   5  |   +--+--->  |   2  |   /  |
         +---+      +------+------+      +------+------+      +------+------+

         +---+
 current | / |
         +---+
Because current has become null, we break out of the loop having produced the following output:

        3
        5
        2
I pointed out that the corresponding array code would look like this:

        int i = 0;
        while (i < size) {
            System.out.println(elementData[i]);
            i++;
        }
Assuming you have some comfort with array-style programming, this might give you some useful insight into linked list programming. There are direct parallels here in terms of typical code:

Array/List Equivalents
Description Array code Linked list code
go to front of the list int i = 0; ListNode current = front;
test for more elements i < size current != null
current value elementData[i] current.data
go to next element i++; current = current.next;

In fact, knowing that we like to use for loops for array processing, you can imagine writing for loops for the processing of linked lists as well. Our code above could be rewritten as:

        for(ListNode current = first; current != null; current = current.next) {
            System.out.println(current.data);
        }
Some people like to write their list code this way. I tend to use while loops for list code, but it's an issue of personal taste.

Then I spent some time talking about how we are going to use linked lists to define a new class called LinkedIntList that will have the same methods of the IntList class. I pointed out that this is our first example of the concept of "data abstraction". We have spent a lot of time studying how to create a class called IntList whose instances each store a list of integers by keeping track of an array and a size. Now we are going to see how the same functionality can be built using a linked list.

I asked what data fields will be needed and there were several suggestions. I said that for now we'll stick with the minimum and in this case the only data field you need is a reference to the front of the list:

public class LinkedIntList { private ListNode front; <methods> } So these are two fundamentally different ways to get the same kind of behavior. From the point of view of a client, these two classes accomplish the same thing and in some sense we don't care how each does its job as long as it does it correctly. But from the point of view of the implementor of the class, the details are quite different.

Next I turned to the question of how we would implement the appending add operation from the old IntList class for our new LinkedIntList class. The method is supposed to append the new value at the end of the list, which means we have to locate the end of the list. Suppose we have the list above and want to add the value 4 at the end of the list. First we have to get there. So here's a start:

        ListNode current = front;
        while (current != null)
            current = current.next;
What happens is that the variable current moves along the list from the first to the last node until it becomes null, leaving us in this situation:

                    +------+------+      +------+------+      +------+------+
         +---+      | data | next |      | data | next |      | data | next |
   front | +-+--->  |   3  |   +--+--->  |   5  |   +--+--->  |   2  |   /  |
         +---+      +------+------+      +------+------+      +------+------+

         +---+
 current | / |
         +---+
Some people think that we could then execute this line of code to complete the task:

        current = new ListNode(4);
But that won't work. It leaves us in this situation:

                    +------+------+      +------+------+      +------+------+
         +---+      | data | next |      | data | next |      | data | next |
   front | +-+--->  |   3  |   +--+--->  |   5  |   +--+--->  |   2  |   /  |
         +---+      +------+------+      +------+------+      +------+------+

                    +------+------+
         +---+      | data | next |
 current | +-+--->  |   4  |   /  |
         +---+      +------+------+
This allocates a new node, but this new node has no connection to the original list. The list is still composed of 3 nodes linked together. This fourth node has been constructed, but hasn't been properly linked into the list.

We didn't have time to explore this in detail, but I mentioned that a key idea in linked list programming is that often we need to stop one early. Someone mentioned that we could accomplish this by changing the test to:

        while (current.next != null)
I mentioned that this is the right idea and that we'd discuss it more in section and in Friday's lecture.


Stuart Reges
Last modified: Sun Jan 22 16:39:12 PST 2006