Lab 7: First try at classes

The goal of this lab is to experiment writing simple classes and also to discover the use of instance methods.

I. Complex numbers

1. Introduction and set up

Complex numbers are numbers that have a real part and an imaginary part (factor of squart root of -1 or i). Here are some examples of complex numbers:

5.3
29.14 + 4.3 i
8.53 i
3.2 - 2.1 i

Some programming languages, like Fortran, have a primitive complex number type. Java does not. We will write a class for complex numbers (including some operations) in this lab.

Make a directory for this lab and then copy two directories from the course directory. These two directories contain two versions of the complex number program, which you will explore and modify.

mkdir lab7
cd lab7
cp -r /homes/tvandrun/Public/cs235/lab7/complex1 .
cp -r /homes/tvandrun/Public/cs235/lab7/complex2 .
cd complex1

(The -r part means you are copying a whole directory.)

2. Complex numbers, first try

Open ComplexDriver.java and Complex.java in xemacs. Look at Complex.java, and understand what it means. An instance of the Complex class has two parts to it, called real and imag, both doubles. You will not need to modify this file.

Glance down ComplexDriver and get a basic feel for what it does. We input two complex numbers from the user, print them out, and calculate their sum, product, and quotient. There are methods to do these operations, but not all of them are finished.

A. First, implement the method compToString() to make a string representation of a complex number. Currently it returns an empty string. If your number has a 3.1 real part and 5.7 imaginary part, it should produce a string like 3.1 + 5.7 i.

After writing this method, compile and test the program to see if it correctly prints out the two complex numbers that the user inputs. Don't worry about what the rest of the program produces-- you will implement the other methods in the next section.

B. Now, inspect the method plus() which is written for you, and, following that example, implement the methods times() and dividedBy(). You may need to do some algebra to figure out how to compute these.

When you have written them, compile your program and test.

3. Complex numbers, second try

Now we will see some expanded capabilities of classes and a better way to implement complex numbers. Move into the complex2 directory.

cd ../complex2

Inspect the new versions of the file Complex.java ComplexDriver.java. You'll first notice that ComplexDriver.java is shorter and Complex.java is longer. If you look closely, you'll see that the methods that used to be in ComplexDriver are now in Complex. There are several new things to draw your attention to.

A. Notice the piece in Complex.java that looks like

    public Complex(double realPart, double imagPart) {
        real = realPart;
        imag = imagPart;
    }

This looks like a method except that it has the same name as the class does and it has no return type. This is called a constructor. It's a special method that gets called when the class is instantiated. Its primary purpose is to initialize the instance variables.

Notice the instantiations of Complex in ComplexDriver:

        Complex comp1 = new Complex(real, imag);   // The first complex number

        Complex comp2 = new Complex(real, imag);   // The second complex number

These pass values to the constructor, just like to a method, which the constructor uses in initializing the instance variables.

B. As we'll see in the coming days, the most interesting part of a class is that it is used to package data and functionality together. Notice in Complex that we have the following:

    public String toString() {
        if (real == 0 && imag == 0)      
            return "0";
        else if (real == 0)
            return imag + "i";
        else if (imag == 0)
            return real + "";     
        else
            return real + " + " + imag + "i";
    }

If it looks like a method, that's because it is. But it's not a static method, it's an instance method. Just as classes have instance variables which define what information is stored for each instance of the class type, classes also have instance methods which define the operations available for this type.

Since an instance method defines an operation for this type, it needs access to the components of the type--so the instance variables are in scope inside an instance method. But since each instance of the type has its own set of these instance variables, who's instance variables are these?

Look at the invocation of toString() in ComplexDriver:

        System.out.println("First complex number is " + comp1.toString());
                                                        ^^^^^^^^^^^^^^^^

Notice that comp1 does not appear inside the parentheses, but instead it appears before the name of the method, with a dot separating them. Notice also how similar this is to the dot notation we use for access to the instance variables of an object. What this means is that we think of the method toString() being called "on" the instance comp1. Moreover, when toString() is called on comp1, it's comp1's instance variables that are in scope inside toString(). Likewise in the invocation comp2.toString(), the method operates on comp2's instance variables.

We call the instance on the left side of the dot the receiver of the method invocation. The idea behind that term is that we can think of the method invocation as a message that is being sent to the object (in this case the message is, "give a string representation of yourself"). The object receives that message and responds to it by returning a value computed from its instance variables.

At this point, compile and test the program. Not everything is working yet, but you can at least observe how toString() works.

C. The method plus() is different because it requires two Complex objects. The way we handle this is that the first addend will be the receiver, and the second addend will be the parameter. (We call the parameter other because it is the other complex number, besides the receiver.) Inside plus() real and imag refer to those variables in the receiver, whereas we refer to the instance variable in the other addend by the old way, other.real and other.imag.

Following the example of plus(), write bodies for the methods times() and dividedBy() (delete the lines "return null;"). Compile and test.

II. A game clock

1. Introduction and set up

The second example in the lab is a very different kind of object, one that models a game clock, such as a sporting event. This will give you a feel for the range of things that objects can be used to model or simulate.

Make a new directory for this (move up out of the complex2 directory) and copy a directory from the course directory containing some starter code.

cd ..
cp -r /homes/tvandrun/Public/cs235/lab7/gameclock .
cd gameclock

First try running the game clock program.

java -cp .:gc.jar GC &

(Ask me after class if you want to know what the -cp .:gcjar stuff means.)

A window will pop up (yes, this is your first program with a graphical user interface (GUI)--unless you did the knight game extra credit) representing the clock. You'll notice that the clock has a current time left, buttons for stopping and starting the clock, for adding seconds, ten seconds, and minutes, and for setting the clock to a specific time.

Try the buttons out. You'll see that although you can add time to the clock, the button to start it running doesn't work, and neither does the button to set the clock to a specific time. You will implement these.

2. The GameClock class

Open the file GameClock.java. You will notice the class has three instance variables: The time left on the clock (timeLeft), the time-of-day when we updated the time left (timeMark), and whether or not the clock is currently running (running). Internally we keep time by milliseconds (even though the clock will display only in seconds). We also use the long type instead of int, since that's what the Java method System.currentTimeMillis() uses.

Look also at the instance methods. You'll notice that the methods correspond to the buttons on the window. I've written the GUI component to this program so that the appropriate methods will be invoke on the object whenever the user clicks on the buttons.

The methods addSecond(), addTenSeconds(), and addMinute() are written for you. Figure out how they work. The method getTime() looks like it's written, but it's actually incomplete.

3. Your task

Here is how the clock should work:

When the clock is started, we record the time-of-day (we call System.currentTimeMillis()) and set the clock's state to be "running." Whenever we are asked for the current time on the clock (and we will be asked continually, several times a second), then we find the current time and subtract the recorded time from it. This gives us how much time has elapsed since the last time we checked the time-of-day, and how much we should decrement the time left on the game clock.

In light of this,

Compile GameClock.java, and run the program as indicated above.

III. Turn in

In a typescript,


Thomas VanDrunen
Last modified: Thu Oct 8 08:25:49 CDT 2009