Objectives

Students who complete this lab will demonstrate that they can:

In this lab, you’ll work with basic class design and implementation in Java.

Exercise 9.1
  1. Create an appropriate package for lab 9.
  2. Create a new class named Fraction. This class will not be a Java application, so it does not need a main() method.
It is likely that your IDE has automatically created a file named 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.

Implementing Class Attributes

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.

Exercise 9.2

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.

Implementing Class Behaviors

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.

Exercise 9.3

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.

Constructors

We construct class objects using constructor methods. Constructors are very much like normal functions except that they:

Default Constructor

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.

Exercise 9.4

Build a default constructor for your fraction class. Implement the following algorithm:

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.

Explicit-Value Constructor

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.

Exercise 9.5

Build the explicit-value constructor for your fraction class. Implement the following (slightly more involved) algorithm:

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:

new Fraction(1/2)    // Incorrect - This only has one argument (whose value is 0!)
The 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.

Accessor Methods

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.

Exercise 9.6

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.

Mutator Method

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.

Exercise 9.7

Implement a fraction simplification method.

Start by copying the computeGCD() definition shown above directly into your fraction class.

Now, implement the following algorithm:

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.

Object Interaction

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.

Multiplication

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) .

Exercise 9.8

Build a fraction multiplication method that implements the following algorithm:

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).

Checking In

Submit all the code and supporting files for the exercises in this lab. We will grade this exercise according to the following criteria: