CSCI 235 Lab 8: A first try at classes

So far, we have used classes as modules: ways of dividing a program into parts. In this lab, we will begin to explore how we can classes to create new types in Java.

In what we have done so far, every method we have written has been static. When we declare something as static, it means that there is exactly one of them, and it is associated with the class.

When we declare a variable or method without specifying that it is static, we are saying that there should be one for every value of the type; the way we say that about Java is that there will be one for every instance of the class. So we will talk about “instance variables” and “instance methods”. Creating a value of the type is called instantiation, and it is done using a special method called a constructor. The constructor’s job is to initialize the instance variables. It looks much like other methods, except that (a) it does not have a return type, and (b) it has the same name as the class. The constructor is invoked in connection with new, which indicates that a new instance is to be created.

1. Setup

Change into the appropriate directory, and then clone the repository for this week’s lab:

$ hg clone /cslab/class/csci235/labs/lab8

There will be two subdirectories in you lab8 directory, one for each part of this lab.

2. Fractions

In the first part of this lab, we are going to write the methods for a class that provides us with a type Fraction. So change into the directory fraction.

Look at the file FractionsDriver.java. This provides a simple main method that turns four command-line arguments into two fractions and then performs several operations on them. You will not need to modify this file, but you should understand how it constructs and manipulates the fractions. To run it on the values 12 and 34, you would run it as

$ java FractionsDriver 1 2 3 4

When you have completed this section, it will print the two fractions followed by their sum, difference, product and quotient. If you compile and run it now, the output will be disappointing.

Now look at Fraction.java, which is an incomplete implementation of a fraction type. The instance variables and constructor have been provided, but you will need to write the rest of the methods.

A. First, implement the method toString() to make a string representation of a fraction. Currently it returns "not implemented". If your fraction is 3
2, it should produce the string "3/2".

You should now be able to test your program, verifying that it prints correctly the numbers that were input. The result may not be very pretty if the denominator is negative. And don’t worry for now about how a fraction such as 4
2 looks.

(You may want to commit this to your repository when you are satisfied that it works correctly.)

B. Now take a look at the product() method, and find the place in FractionDriver.java where it is invoked.

Fraction product = frac1.product(frac2);

This call involves two instances of class Fraction. You should recognize that here frac1 is the receiver of the call, while frac2 is an actual parameter. You can think of this call as sending a message to frac1 asking it, “give me the result of multiplying yourself by frac2.”

You need to fill in the body of product(). Note that you refer to the instance variables of the receiver with unqualified names, while you refer to the instance variables of the parameter other using qualified names, such as other.numer.

Compile and test. (And, probably, commit.)

C. In similar fashion, complete the methods sum() and diff(). The method quotient() has been written for you, but you need to write reciprocal() for it to work.

Compile, test, and commit.

D. Fill in asDouble().

E. It would be better if we ensured that every Fraction instance were kept in a standard, simplest form. The method simplify() should convert a fraction to this standard form, in which the greatest common denominator of the numerator and denominator is 1 (i.e., it is reduced to lowest terms), and the denominator is always positive. Complete simplify(), then uncomment the call to it at the end of the constructor. (A static method gcd() has been provided.)

Don’t forget to commit.

3. A game clock

The second example in the lab is a very different kind of object, one that models a game clock, as for a sporting event. This will give you a feel for the range of things that objects can be used to model or simulate. This is also an exercise in implementing a class when you can not look at the program that is the client.

A. Change to its directory by moving up one directory, and then down:

$ cd ..
$ cd gameclock

First try compiling and running the game clock program.

$ javac GameClock.java
$ java -cp .:gc.jar GC &

(You don’t need to worry right now about what the -cp .:gc.jar stuff means.)

A window will pop up (yes, this is your first program with a graphical user interface, or GUI) 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, nor does the button to set the clock to a specific time. You will implement these.

B. 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 (lastUpdatedTime), 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, because that is what the Java method System.currentTimeMillis() returns.

Look also at the instance methods. You should notice that the methods correspond to the buttons on the window. The GUI component of this program has already been written (it is hiding in the gc.jar file) so that the appropriate methods will be invoked 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() has also been written, but it depends on the method update(), which you will need to complete.

C. Your GameClock needs to count down at the correct rate as determined by the system’s built-in clock, which you can read by calling System.currentTimeMillis().

The clock works by maintaining a class invariant:

Notice that this invariant involves two quantities that are not always captured in the instance variables: the time remaining (what the clock should display) and the current time.

There are three steps for you to complete:

  1. Complete startStop(): it should toggle the running variable and update the instance variables so that the timer shows the correct time remaining when the clock stops.

    To do that, it needs to subtract the amount of time it was running from timeLeft—which looks a lot like the formula above, if lastUpdatedTime was set to the current time when the clock started. So that should let you also figure out how to set the instance variables when the clock starts.

    At this point, you should be able to run the program; the timer will count down when you start it, and it should show the updated time left after you stop it. It will not update the display while running, though.

  2. The portion of the program that you cannot see will call getTime() many times each second in order to update the display. That method in turn calls update(), which should change the instance variables timeLeft and lastUpdatedTime while maintaining the invariant.

    Pay attention to the comment, and make update() stop the clock when time runs out.

    The display on your clock should now count down while the clock is running.

  3. Write setTime(). When the “Set” button is clicked, the text from the box next to it will be passed (as a string) to setTime(). You may assume that the text is an integer indicating the number of minutes to which to set the clock. Extra credit: if the string is only an integer, interpret it as minutes; but if the string is an integer, a colon, and another integer (such as “5:15”) intrepret it as minutes and seconds. Note that setTime() will be called only if the string is in one of those formats.

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

4. Turn in

Make a typescript as you compile and run the FractionsDriver program for an interesting set of values. (Don’t forget to end the script.) It doesn’t make sense to make a script of the gameclock program.

Hand in your three files (typescript, fraction/Fraction.java, and gameclock/GameClock.java) electronically as lab8.