CSE143 Notes for Friday, 11/18/05

I began by reviewing the "x = change(x)" idiom. I gave the following sample client code:

        ArrayList<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("there");
        change(list);
        System.out.println(list);
This code constructs an ArrayList<String>, adds two Strings, then calls the change method, then prints out the list. Suppose that the change method is defined as follows:

        public void change(ArrayList<String> x) {
            x.add("howdy");
            x = new ArrayList<String>();
            x.add("hello world");
        }
The question is, what happens to the list that is passed as a parameter? In the client code, we introduce a variable called "list" that refers to an ArrayList<String> and we add two Strings to it:

     +---+     +------------------------------+
list | +-+-->  | ArrayList: "hello", "there"  |
     +---+     +------------------------------+
When we call the method, we set up a parameter called x. I mentioned that parameter passing in Java is potentially very confusing. There are different parameter passing techniques. One is known as "call by value" or simply "value parameters". Another is known as "call by reference" or "reference parameters." If you ask an audience of computer scientists which kind of parameter passing you get for Java objects, half the audience will raise their hand for the one answer and half will raise their hand for the other answer.

That's because they are each partly right. The mechanism itself is a value parameter mechanism. In other words, we make a copy of whatever is passed to the method. But Java objects are always stored as references, so the thing that will be passed to us is a reference to the object. In other words, Java involves passing references to objects using a value parameter mechanism.

In terms of our example, the variable "x" would be set up as a copy of the variable "list". The variable "list" stores a reference to an ArrayList<String>, so x will store this same reference:

     +---+     +------------------------------+
list | +-+-->  | ArrayList: "hello", "there"  |
     +---+     +------------------------------+
                            ^
     +---+                  |
   x | +-+-->------->-------+
     +---+
Thus, when we execute this line of code:

        x.add("howdy");
We end up changing the client code's list:

     +---+     +---------------------------------------+
list | +-+-->  | ArrayList: "hello", "there", "howdy"  |
     +---+     +---------------------------------------+
                            ^
     +---+                  |
   x | +-+-->------->-------+
     +---+
But when we execute the next two lines of code, we are changing what x refers to:

        x = new ArrayList<String>();
        x.add("hello world");
This has no effect on the variable "list":

     +---+     +---------------------------------------+
list | +-+-->  | ArrayList: "hello", "there", "howdy"  |
     +---+     +---------------------------------------+

     +---+     +--------------------------+
   x | +-+-->  | ArrayList: "hello world" |
     +---+     +--------------------------+
When the method finishes executing, the variable x goes away and we come back to the client code and perform a println on "list", which produces the following output:

        [hello, there, howdy]
We can see from this that when you pass an object as a parameter, you are able to change the object itself, but not the reference to the object. In our case, we could modify the actual ArrayList<String> that list refers to, but we couldn't make list refer to a different ArrayList<String>. That was true even though we caused the parameter x to point to a different ArrayList<String>.

Some languages have what is known as a "reference" parameter that will actually change. For example, in C++ we could make this work by inserting the character "&" after the parameter type:

        public void change(ArrayList<String> & x) {
            ...
        }
Java does not have true reference parameters. But we can simulate the effect of a reference parameter by rewriting the change method to return x at the end of the method. This means that its return type can no longer be "void". It has to match the type of x (in this case, ArrayList<String>):

        public static ArrayList<String> change(ArrayList<String> x) {
            ...
            return x;
        }
This is a general technique that we can use to allow us to change the value of the parameter x. The rest of the code of the method would be unchanged:

        public static ArrayList<String> change(ArrayList<String> x) {
            x.add("howdy");
            x = new ArrayList<String>();
            x.add("hello world");
            return x;
        }
But to make this work, we have to change the call on the method as well. It returns a new value for x, which means we have to rewrite this line of client code:

        change(list);
to be:
        list = change(list);
In this new version of the program, we reassign the variable list to point to what "x" points to at the end of the change method's execution (a list with just the String "hello world" in it). So in this new version, when we execute the println, we get this output:

        [hello world]
I mentioned that this is a very important technique in Java that simplifies a lot of binary tree code. It allows us to simulate the effect of a single reference parameter. Obviously since we can only return one value from a method, we can only simulate a single reference parameter. But that's enough for most of these binary tree tasks. I used this technique in the "insert" method that I wrote for the binary search tree class.

I spent most of the rest of the lecture discussing the next programming assignment. The idea is to construct a binary tree that has information about a number of different kinds of things. We use yes/no questions to distinguish between them.

Then I reminded people of the basic operation the program is performing. For example, initially it constructs a tree with just one leaf node containing "computer":

         +---+      +------------+
    root | +-+-->   | "computer" |
         +---+      +------------+
In this particular program, leaf nodes contain the names of objects and branch nodes contain questions. Whenever we get to a leaf node, we have no choice left but to guess that particular thing. So if we use the tree above, we'd always start by asking whether the object happens to be computer. If the user says yes, then we've correctly guessed the object and we give the message:

        Great, I got it right!
If this isn't their object, then we expand our tree to incorporate this new kind of object as well. To do that, we need to replace the leaf node with a branch node that has a question and we want that node to have two leaves: our old object ("computer") and their new object. We start by asking the user what their object is. Suppose they say they were thinking of "pen". Then we ask the user for a question that distinguishes between their object and our object. Suppose they say, "Does it have ink?". The plan is to replace our old leaf node with a branch node containing their question and with the old and new objects as leaves. But we don't know which one to put to the left and which one to put to the right. To figure that out, we have to ask the user what the answer is for their object. Suppose they say that the answer for "pen" is yes. I mention in the assignment writeup that we'll follow the convention that yes answers go to the left and no answers go to the right. So I'd replace the old root with this new tree:

         +---+      +---------------------+
    root | +-+-->   | "Does it have ink?" |
         +---+      +---------------------+
                           /   \
                          /     \
                  +-------+     +------------+
                  | "pen" |     | "computer" |
                  +-------+     +------------+
Then the process starts again. If the user wants to play again, you ask them to think of an object. Then you ask them if it has ink. Suppose they say yes. That means you go to the left and find that you're at a leaf node. Whenever you get to a leaf node, you have no choice but to make that guess. So you ask them whether they're thinking of a pen. If they say yes, you'd be done. If not, you ask them what they're thinking of. Suppose they're thinking of "squid". Then you ask them for a question that distinguishes a pen from a squid. Perhaps they say, "Can you write with it?". And then you ask what the answer is for their object. The answer to that question for "squid" is no. That means that "squid" is put into the right tree and "pen" is put into the left tree with this new question as the root:

                  +---+      +---------------------+
             root | +-+-->   | "Does it have ink?" |
                  +---+      +---------------------+
                                    /   \
                                   /     \
        +--------------------------+     +------------+
        | "Can you write with it?" |     | "computer" |
        +--------------------------+     +------------+
                    /   \
                   /     \
           +-------+     +---------+
           | "pen" |     | "squid" |
           +-------+     +---------+
This process continues as long as the user wants to keep guessing. When the program finishes executing, you write out the contents of the tree to a file using a preorder traversal. That way, if the user wants to, they can start the next time with this as the initial tree. That would allow you to grow this tree bigger and bigger each time the game is played.

I also discussed the use of the Scanner. When using a Scanner, it is best to either use all token-based processing methods like nextInt() and next() (along with the corresponding boolean methods like hasNextInt() and hasNext()) or to stick to just the line-based processing method nextLine() and its corresponding boolean method hasNextLine(). If you mix the two modes, you're likely to get odd results. So my advice is to use nothing but calls on nextLine(). I purposely designed the assignment so that it could be done that way, which would mean that students didn't have to torture over how to use the Scanner properly.

I also mentioned that you will have a data field of your class for the console Scanner that you'll initialize once to read from the console:

        console = new Scanner(System.in);

Stuart Reges
Last modified: Mon Nov 28 13:46:46 PST 2005