CSE143 Notes for Monday, 2/27/06

I began by reviewing the "x = change(x)" idiom. I decided to use the built-in Point class in Java for an example. It is part of the java.awt package and is used to store the (x, y) coordinates of a pixel on the screen. As a result, it takes two integers as arguments. So you construct points like this:

        Point p1 = new Point(8, 17);
        Point p2 = new Point();
The first line of code constructs a point with coordinates (8, 17). The second constructs a point with default coordinates (the origin--0, 0).

One of the methods of the Point class is called translate. It takes a delta-x and a delta-y as parameters and it changes x and y by the given amounts. For example, given the points above, if you say:

        p1.translate(3, 4);
        p2.translate(5, 2);
The first line adds 3 to the x-coordinate of p1 and 4 to its y-coordinate, leaving the Point at location (11, 21). The second line adds 5 to the x-coordinate of p2 and 2 to its y-coordinate, leaving it at (5, 2).

The term "translate" comes from mathematics. We can imagine translating a point many different times, moving it from one location to another to another. Then I gave the following sample client code:

        Point p = new Point(3, 8);
        change(p);
        System.out.println(p);
This code constructs a Point and then calls the change method, then prints out the point. Suppose that the change method is defined as follows:

        public void change(Point x) {
            x.translate(2, 3);
            x = new Point(2, 9);
            x.translate(2, 2);
        }
The question is, what happens to the point that is passed as a parameter? In the client code, we introduce a variable called "p" that refers to a Point and we add two Strings to it:

          +---+     +--------------+
        p | +-+-->  | x: 3 | y : 8 |
          +---+     +--------------+
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 "p". The variable "p" stores a reference to a Point, so x will store this same reference:

          +---+     +--------------+
        p | +-+-->  | x: 3 | y : 8 |
          +---+     +--------------+
                           ^
          +---+            |
        x | +-+-->---->----+
          +---+
Thus, when we execute this line of code:

        x.translate(2, 3);
We end up changing the client code's Point:

          +---+     +---------------+
        p | +-+-->  | x: 5 | y : 11 |
          +---+     +---------------+
                           ^
          +---+            |
        x | +-+-->---->----+
          +---+
But when we execute the next line of code, we are changing what x refers to:

        x = new Point(2, 9);
This has no effect on the variable "p":

          +---+     +---------------+
        p | +-+-->  | x: 5 | y : 11 |
          +---+     +---------------+

          +---+     +--------------+
        x | +-+-->  | x: 2 | y : 9 |
          +---+     +--------------+
Thus, the final line of code in the method:

        x.translate(2, 2);
Changes the new Point object, not the one that "p" refers to:

          +---+     +---------------+
        p | +-+-->  | x: 5 | y : 11 |
          +---+     +---------------+

          +---+     +---------------+
        x | +-+-->  | x: 4 | y : 11 |
          +---+     +---------------+
When the method finishes executing, the variable x goes away and we come back to the client code and perform a println on "p", which produces the following output:

        java.awt.Point[x=5,y=11]
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 Point that p refers to, but we couldn't make p refer to a different Point. That was true even though we caused the parameter x to point to a different Point.

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(Point & 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, Point):

        public Point change(Point 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 Point change(Point x) {
            x.translate(2, 3);
            x = new Point(2, 9);
            x.translate(2, 2);
            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(p);
to be:
        p = change(p);
In this new version of the program, we reassign the variable p to point to what "x" points to at the end of the change method's execution (a new Point). So in this new version, when we execute the println, we get this output:

        java.awt.Point[x=4,y=11]
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.

Then I started a new topic. I want to discuss inheritance and what it can be used for. Before the midterm we talked about the mechanics of inheritance, but we haven't yet talked about where inheritance is helpful.

I started with a simple example. I reminded people that earlier in the quarter we talked about stacks. We had an interface that defined the various operations:

        public interface Stack<E> {
            public void push(E value);
            public E pop();
            public boolean isEmpty();
            public int size();
        }
And we had a class called ArrayStack that implemented this interface:

        public class ArrayStack<E> implements Stack<E> {
           ...
        }
I posed the following question. Suppose that you want a stack that behaves slightly differently. In particular, you'd like it to be the case that every time that push is called, it actually pushes two of the value onto the stack. So it should have a kind of stuttering effect. In all other respects, it should behave like a normal stack.

How would you make this change? I was hoping that someone would say that we'd edit ArrayStack to behave this way, but the class was too clever for me. There is a mantra that you'll hear in the object-oriented programming community that we want "additive--not invasive--change." Programmers are very used to the idea of doing surgery on existing code. We call that "invasive change" because it involves cutting open an existing class and rearranging it. This is like doing surgery on a person. Sometimes you have no choice but to perform surgery, but it can be very traumatic to do so. As a result, we want to avoid it if we can.

Inheritance provides a great mechanism for avoiding surgery. We can construct a new class called StutterStack that inherits from ArrayStack:

        public class StutterStack<E> extends ArrayStack<E> {
            ...
        }
Just with this simple inheritance declaration, we get a class that behaves just like an ArrayStack. Now we can say what is different about our version. We want this version of the stack to push everything twice. We can easily define that in terms of the existing "push" in the super class:

        public class StutterStack<E> extends ArrayStack<E> {
            public void push(E value) {
                super.push(value);
                super.push(value);
            }
        }
This is all you have to do. Remember that when you inherit, you have the option to override a method. In this case, we are overriding the push method to give it a different behavior. But you still have access to the method you are overriding by using the "super" keyword (in this case, calling super.push).

Now we have a class that we can use in place of an ArrayStack that will have the stuttering behavior we wanted. For example, we could write the following code:

        Stack s = new StutterStack();
        s.push("hello");
        s.push("there");
        while (!s.isEmpty())
            System.out.println(s.pop());
And we would get the following output:

        there
        there
        hello
        hello
I mentioned that this is such an important application of inheritance that I'll be asking a final exam question like this. The idea is that inheritance is very good at capturing variations. Using inheritance we can make a minor change to an existing class without changing the original class (additive--not invasive--change).

As a second example, I said to suppose that you wanted to have a Point object that remembers how many times it has been translated. How can you do that?

Obviously the answer will involve inheritance again. But this time it is not as simple as defining a new method. We'll also need a new data field. To remember the number of times it has been translated, the point will need an integer counter. So it will look like this:

        import java.awt.*;
        
        public class MyPoint extends Point {
            private int count;
        
            ...
        }
I don't have a good short name for this class. I don't want to call it PointThatRemembersHowManyTimesItHasBeenTranslated. So instead I just called it "MyPoint." We'll want to override the translate method. It can call the original translate method because we'll still want to do that original behavior, but we can also increment our counter:

        public void translate(int dx, int dy) {
            count++;
            super.translate(dx, dy);
        }
So now we have a data field to remember this and we've modified the translate method to increment the counter each time it is called. The problem is that this is a private data field that we can't see from the outside. So if we want to allow someone to actually see the value of the counter, we'd have to add a "getter" method:

        public int getTranslateCount() {
            return count;
        }
This provides a pretty good version of the class, although there are a few details that we still have to work out. Here's what we have so far:

        import java.awt.*;
        
        public class MyPoint extends Point {
            private int count;
        
            public void translate(int dx, int dy) {
                count++;
                super.translate(dx, dy);
            }
        
            public int getTranslateCount() {
                return count;
            }
        }
Someone suggested that we need to think about the initialization of the counter. I said that we certainly need to think about it, but in this case, we're okay. Remember that Java initializes all data fields to the 0-equivalent. So the counter will be initialized to 0. But the details I'm thinking about do have to do with the constructing of a Point.

So I mentioned to people a detail of inheritance that we haven't yet discussed. What do you get when you inherit from another class? You inherit the state and behavior of the other class. But not entirely. You inherit most methods from the class that you extend, but not all of them. Someone said "constructors" and I said, "That's right." You don't inherit constructors from a class that you extend.

I decided to use this as an opportunity to review the details of constructors and to discuss how inheritance factors into this. So I asked people what Java does when you don't define any constructors. The answer is that Java defines one for you. We refer to it as the "default" or "paren paren" or "zero argument" constructor. For our class called MyPoint, it would be as if we had included the following method in the class:

        public MyPoint() {
            // nothing to do
        }
But this would be the only constructor for the class. In particular, we wouldn't be able to say something like:

        Point p1 = new MyPoint(8, 17);
I then mentioned that every constructor makes a call on a superclass constructor, whether you do so explicitly or not. The syntax for calling a superclass constructor is very similar to the syntax for one constructor calling another, except for the fact that you use the keyword "super" instead of the keyword "this". For example, if you want to call the default constructor of the superclass, you say:

        super();
We have been writing classes for a long time without including such calls. The reason that works is that if you don't call a superclass constructor, then Java does it for you. And by default it calls the default constructor. So if you don't include a call on the superclass constructor, Java will insert the call above for you.

That means that our MyPoint constructor isn't quite as empty as we had thought. It really does the following:

        public MyPoint() {
            super();
        }
Remember that Java inserted this constructor for us because we didn't define one of our own. What would happen if the Point class did not have a default constructor? In that case, we would have gotten a weird error message:

MyPoint.java:3: cannot find symbol
symbol  : constructor Point()
That would seem like an odd error message because we never call Point() anywhere in our code. But it makes sense when you understand that Java is doing it for us. Without a constructor, Java will give us a default constructor. And since we didn't say how to call a superclass constructor, it makes the call super().

The Point class actually does have a default constructor, so we don't get this message. But it's important to remember that this will occur. Many classes do not have a default constructor, and then you must call a superclass constructor.

In our case, we want to have a constructor that takes two integers that specify x and y. Our class doesn't keep track of x and y, that's what the Point class does. So all we want to do is to pass the values up the chain to the Point class' constructor that takes two arguments. We can also initialize the counter just to be clear:

        public MyPoint(int x, int y) {
            super(x, y);
            count = 0;
        }
Now we can construct MyPoint objects with two ints. Unfortunately, now we can no longer construct the origin by asking for a "new MyPoint()". Java defines a default constructor if you don't define any of your own. But if you have even one constructor in your class, then Java assumes that you know what you're doing and that you would have defined a default constructor if you wanted one. So if we really want that for the MyPoint class, we have to specifically include it. In this case, we can call the two-argument constructor in the MyPoint class:

        public MyPoint() {
            this(0, 0);
        }
Putting it all together, here is the definition of MyPoint:

        import java.awt.*;
        
        public class MyPoint extends Point {
            private int count;
        
            public MyPoint() {
                this(0, 0);
            }
        
            public MyPoint(int x, int y) {
                super(x, y);
            }
        
            public void translate(int dx, int dy) {
                count++;
                super.translate(dx, dy);
            }
        
            public int getTranslateCount() {
                return count;
            }
        }
I reminded people that there would be a final exam question like this and I mentioned that we discuss this more in Wednesday's lecture and in Thursday's section.


Stuart Reges
Last modified: Wed Mar 1 09:24:33 PST 2006