The goal of this lab is to experiment writing simple classes and also to discover the use of instance methods.
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:
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.)
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.
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.
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.
GameClock
classOpen 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.
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,
startStop()
so that it toggles the running
boolean variable
and, when appropriate, records the time.
getTime()
so that, if the clock
is currently running, it will update the time left
on the clock before returning the string representation.
setTime()
method.
Write setTime()
so that it interprets this
string and sets the time left on the clock appropriately.
Assume this text is an integer and interpret it as the
number of minutes (extra credit: assume nothing; interpret it as minutes
if it is just an integer, but if it is an integer, colon, another integer
(for example, "5:15"),
interpret them as minutes and seconds; if it is neither of these, ignore
the request).
Compile GameClock.java
, and run the program
as indicated above.
In a typescript,
Complex.java
and ComplexDriver.java
of complex1
.
Complex.java
and
ComplexDriver.java
of complex2
.
GameClock.java
.