Students who complete this lab will demonstrate that they can:
In this lab, you’ll work with basic class design and implementation in Java.
Fraction
. This class will not be a Java application, so it does not
need a main()
method.
Fraction.java
and declared a new class in this file with the same name,
Fraction
. You will fill in the elements of this class definition iteratively and incrementally in this lab.
The state of an object is represented by the values of its attributes. For example an object representing the fraction one-half will have a numerator of 1 and a denominator of 2. These attributes will be implemented as private, instance variables (not public or static) and will be in scope throughout the entire class definition.
At this point, it’s important to think about valid and invalid object states. Because division by zero is undefined, a fraction object must have a denominator that is not zero. This condition must always be true and is thus called an invariant.
Your faction class will need to keep track of the fraction’s numerator and denominator. Add instance variables
to your
Fraction
class to represent these values. Make them integers and use the naming convention in the text by naming them
myNumerator
and
myDenominator
.
Add documentation for your class, including a one-line description of what we are modeling. Use a boolean expression to specify the class invariant with respect to the values of the instance variables, and be sure to include your name and the date as usual.
This class definition should have no compiler errors, but at this point it will not actually run.
The behavior of class objects is specified using methods, including output methods, constructors, accessors, mutators and other utility methods. The remaining exercises address these methods one at a time.
toString()
Method
When Java prints out an object, it automatically calls the
toString()
method defined for that object. A new class can override the default implementation of this function to specify
exactly how it would like its object to be printed.
Implement a
toString()
method that receives nothing from the calling program and returns a string representing the current state of the
fraction object. For example, if the fraction is representing one-half (i.e.,
myNumerator
is 1 and
myDenominator
is 2), then return “1/2”.
Again, this new method definition should have no compiler errors but will not at this point actually run.
We construct class objects using constructor methods. Constructors are very much like normal functions except that they:
The simplest of the constructors is the default constructor. Default constructors receive nothing from the caller and initialize the instance variables to valid, default values.
Build a default constructor for your fraction class. Implement the following algorithm:
myNumerator
and myDenominator
have been declared as
integer instance variables.myNumerator
= 0.myDenominator
= 1.These values are reasonable defaults and they do not violate the fraction class invariant. Add documentation for this method explaining what it does.
Now that you have a class, constructor and output method, create a new
FractionConsole
application class (in the same package as your
Fraction
class) to test your class. Use the following program:
public class FractionConsole { public static void main(String[] args) { Fraction f1 = new Fraction(); // Construct a default fraction object. System.out.println(f1); // Print the object. } }
This program should output
0/1
. Add appropriate documentation to this console program, and save the program so that you can submit it later.
It is likely that programmers will want to create their own fraction objects and to set numerator and denominator themselves. For this, we would like a constructor that accepts initial values as parameters.
Build the explicit-value constructor for your fraction class. Implement the following (slightly more involved) algorithm:
myNumerator
and myDenominator
have been declared as
integer instance variables.numerator
and denominator
.numerator
and denominator
satisfy the
invariant, then:
myNumerator
= numerator
.myDenominator
= denominator
.System.err
.Add documentation to this method explaining what it does.
To test the behavior of this new constructor, add the following code to your console application.
Fraction f2 = new Fraction(1, 2); // Construct a fraction object for one half. System.out.println(f2); // Print the object.
This new code segment should output
1/2
. Plan and implement a few additional test cases to be sure that your explicit-value constructor is working properly.
Comment these tests as Exercise 9.5. In particular, try a division-by-zero case to see that it prints an error
appropriately. After you have verified that your program has the desired behavior, comment out the test case that
causes the program to exit.
Note that when the programmer creates a fraction object representing one-half, they do not pass
1/2
to the explicit-value constructor, as in:
Thenew Fraction(1/2) // Incorrect - This only has one argument (whose value is 0!)
Fraction
class knows very well how to represent and work with fractions; it does not need the programmer to tell it what to do
with the numerator and denominator, all it needs is the (separate!) values for the numerator and the denominator, as
in:
new Fraction(1, 2) // Correct - This passes the numerator and denominator for proper handling by the class.
Classes generally declare their instance variables to be private, but it is nevertheless useful at times for programmers to be able to get those instance values for special uses. This behavior is implemented using accessors, which are simple methods that receive nothing from the calling program and return the value of the instance variable.
Write two accessor methods for your fraction class, one for
myNumerator
and the other for
myDenominator
. Name these methods following the convention for accessors. When they are implemented, document them and add lines
to your console program to test them.
Some methods make changes to the state of a class object. These methods are called mutator methods. For the
fraction class, it will prove convenient to have a mutator method that simplifies the current state of the fraction.
For example, if the fraction is 2/4 (i.e., the numerator is 2 and the denominator is 4), then we’d really like
to be able to simplify the fraction to the value 1/2. You’ll use this method in your explicit-value constructor
to simplify new fraction objects. For example, if a programmer calls
new Fraction(2, 4)
, we would like the explicit-value constructor to automatically simplify this to 1/2.
Simplifying fractions requires that you be able to compute the greatest common divisor of the numerator and the denominator. For example, the numerator 2 and denominator 4 share 2 as their greatest common divisor, which means that we can divide both by 2 to simplify 2/4 to (2/2)/(4/2) = 1/2. The greatest common divisor can be computed using the following recursive method.
/** * This method finds the greatest common divisor of two integers, using * Euclid’s algorithm * * @param alpha one integer * @param beta the other integer * @return the greatest common divisor of alpha and beta. */ private static int computeGCD(int alpha, int beta) { alpha = Math.abs(alpha); beta = Math.abs(beta); int remainder = alpha % beta; while (remainder != 0) { alpha = beta; beta = remainder; remainder = alpha % beta; } return beta; }
This method is marked as
private
meaning that it will only be used by other methods in the fraction class, in this case only by your new simplify
method. It is also marked as
static
, meaning that this single definition can be shared by all fraction objects; this means that the method cannot
directly access instance variables in the class but must, instead, receive its values as parameters.
Implement a fraction simplification method.
Start by copying the computeGCD() definition shown above directly into your fraction class.
Now, implement the following algorithm:
myNumerator
and myDenominator
have been declared as
integer instance variables and computeGCD()
has been defined as shown above.simplify()
)
gcd
= the greatest common divisor of the numerator and denominator. computeGCD(myNumerator, myDenominator)
.
gcd
is not equal to 0 then:
myNumerator
= myNumerator/gcd
.myDenominator
= myDenominator/gcd
.myDenominator
is less than 0 then:
myNumerator
= myNumerator
* -1.myDenominator
= myDenominator
* -1.
Note that this method does not return anything. It is a mutator method that changes the values of
myNumerator
and
myDenominator
but returns nothing.
When this method is complete, document it properly and add an invocation for it in your explicit-value constructor
just after you’ve assigned the values to
myNumerator
and
myDenominator
. The invocation should look like this:
myNumerator = numerator; // These first two statements should already be there. myDenominator = denominator; simplify(); // This simplifies the values provided by the calling program.
With this all in place, test it out by adding the following code segment to your console application:
Fraction f3 = new Fraction(2, 4); // Construct a fraction object for two fourths. System.out.println(f3); // Print the object.
This new code segment should output
1/2
. Plan and implement a few additional test cases to be sure that your explicit-value constructor is working properly
(as usual, comment these tests as Exercise 9.7). In particular, try a case in which both the numerator and
denominator are set to negative numbers; step 3 in the algorithm is a clever bit of coding that should handle
negatives appropriately.
There are times when fractions must interact with other fractions. The methods in this section are not mutator methods. They create a new fraction with the appropriate value.
Multiplying one fraction with another should produce a new fraction with the appropriate numerator and denominator. For example, 1/2 * 2/3 = 2/6 which simplifies to 1/3.
Implementing multiplication requires that we use that odd mechanism common to object-oriented programming languages
without operator overloading in which we must ask one of the fractions to multiply itself by another. The notion for
this is
fraction1.multiply(fraction2)
.
Build a fraction multiplication method that implements the following algorithm:
myNumerator
and myDenominator
have been declared as
integer instance variables and that the explicit-value constructor simplifies the fractions it constructs.multiply()
)
otherFraction
by which to multiply myself.myNumerator
* otherFraction.getNumerator()
, and
myDenominator
* otherFraction.getDenominator()
.
As usual, document this new method, and then include a couple test cases for your multiplication method in your console application (document the tests as Exercise 9.8).
Submit all the code and supporting files for the exercises in this lab. We will grade this exercise according to the following criteria:
toString()
method.
simplify()
appropriately.
multiply()
appropriately.