Lab 6: Building Classes


Most of the problems we have examined have had relatively simple solutions, because the data objects in the problem could be represented using the predefined Java types. For example, we can represent a volume with a double, a name with a String, a vowel with a char, and so on.

The problem is that real-world problems often involve data objects that cannot be directly represented using the predefined Java types. For example, suppose that we know a certain gourmet chef named Pierre whose recipes are written to make 12 servings. The difficulty is

  1. Pierre frequently must prepare a dish for numbers of customers other than 12 (e.g., 1 customer = 1/12 of a recipe, 2 customers = 1/6 of a recipe, 15 customers = 15/12 = 5/4, and so on.)
  2. Pierre's recipes are written using fractions (e.g., 1/2 tsp., 3/4 cup, etc.) so that he must multiply fractions to reduce the size of a recipe, and
  3. Pierre is so poor at multiplying fractions, that he has hired us to write a program that will enable him to conveniently multiply two fractions.

We have provided two programs for today's exercise: and In the first part of the exercise, we will do what is needed to get operational, and in the second part of the exercise, we will extend the first part so that is operational.


Getting Started

Begin by making a new project folder in which to store your work for this exercise. Then save copies of the files,,, in your project folder.

If you are using an IDE, create a new project named Pierre in your project folder, and add the files and and the ann package to your project (but not

Open and and take a moment to compare the two programs. Each program implements the same basic algorithm:

   1. Get oldMeasure, the fractional measure to be converted.
   2. Get scaleFactor, the fractional conversion factor.
   3. Compute newMeasure = oldMeasure * scaleFactor.
   4. Output newMeasure.

Note that a solution to Pierre's problem is quite simple, given the ability to define, input, multiply and output fraction objects. The two programs differ only in how they output Fraction objects. Note also that some lines are "commented out" at present. We will be "uncommenting" these lines as we develop the functionality needed in order for such lines to work properly.

The difficulty is that there is no predefined Java type Fraction by which such objects can be defined or operated upon. In such situations, Java provides a mechanism by which a programmer can create a new type and its operations. This mechanism is called the class, which is the subject of today's exercise.


Creating Classes

In Java, a new type can be created by

  1. Defining the data objects that make up the attributes of an object of the new type (i.e., the things of which an object of that type consists); and
  2. Surrounding those definitions with a class structure, which has the form:
       public class TypeName extends OtherType 

    where TypeName is a name describing the new type.

When objects and methods are declared inside the class, the kind of access that will be allowed must be indicated. A class has two basic kinds of access, public and private. The public parts are accessible by all other classes and are typically how methods are declared, and the private parts are accessible only by the class and are typically how class attributes are declared.

To illustrate, suppose that we want to define a new type whose objects can be used to store Cartesian coordinates. Such an object has two attributes: an X-value, and a Y-value, both reals. We can define data objects for these attributes as follows:

   private double myX, myY;

(To distinguish attribute identifiers from other identifiers, we will place the word my at the beginning of an attribute's name.) We then surround them with an appropriately named class structure:

   public class Coordinate extends Object
      private double myX, myY;  

The result is a new type, named Coordinate, which can be used to declare objects, such as a point:

   Coordinate point = new Coordinate();

The object to which point refers then has two real components, one named myX and the other named myY.

In general, the data portion of a class definition can be thought of as following this pattern:

   public class TypeName  extends OtherType   
      private Type1 AttributeList1 ;
      private Type2 AttributeList2 ;
      private TypeN AttributeListN ;

where each Typei is any defined type; and each AttributeListi is a list of the attributes requiring an object of type Typei needed by our new object kind (TypeName).

Now, if we apply this approach to the problem we are trying to solve, we see that we need to identify the attributes of a Fraction object. If we examine a few fractions

   1/2     4/3   4/16  16/4

we can see that each fraction has the form:


where number1 is called the numerator and number2 is called the denominator. The numerator and denominator are different from one fraction to another, and so these quantities must be recorded for any given fraction value; however the / symbol is common to all fractions, and so it need not be recorded as an attribute. A fraction thus has two attributes, its numerator and its denominator, both of which are integers.

Begin editing the file, and define two integer data objects named myNumerator and myDenominator to represent these two attributes. (Yes, you should prepend my to the beginning of each name, for reasons that will be apparent shortly). Be sure to arrange your class so that myNumerator and myDenominator are within the class and are private.

Given this declaration of Fraction, object declarations like this

   Fraction oldMeasure;
   Fraction scaleFactor;

can be thought of as defining two data objects with the following forms:

The objects within a class object that store its state are usually called the attributes of that object.

Note that each object of type Fraction has its own copy of each of the attributes we defined. This is why we preface the names of these attributes with my, to indicate that from the perspective of the object, attributes are characteristics of that object. That is, within the Fraction object named oldMeasure, myNumerator refers to its first data object and myDenominator refers to its second data object. However, within the object named scaleFactor, myNumerator refers to its first data object and myDenominator refers to its second data object. Each object of type Fraction thus has its own separate state.

Since a class may contain an arbitrary number of attributes, a software model can be constructed for virtually any real-world object, simply by defining data objects for each of its attributes, and then surrounding those data objects with an appropriately named class structure.

In the source program, the definition of oldMeasure is currently commented out. Modify your source program so that this declaration is no longer commented out (but the subsequent lines are). Then compile your source program, to test the syntax of what you have written. When it is correct, continue to the next part of the exercise.



Besides having attributes, a class can also have methods which provide a means by which the operations on a class object can be encoded. These methods are different from the static methods we have seen before in that they will work on the attributes of one particular object.


Class Structure and Information Hiding

One of the characteristics of a class is that its attributes are kept private, meaning that a program using the class is not permitted to directly access them. (This is a good idea because a program that directly accesses the attributes of a class becomes dependent on those particular attributes. If those attributes are changed (which is not uncommon in class maintenance), then such programs must also be changed, increasing the cost of software maintenance.)

While it is a good idea for the attributes of a class to be kept private, we want users of the class to be able to perform operations on class objects. As a result, the methods should be declared as public, in contrast to the attributes.

This is the reason for the keywords public and private within the class. A common pattern for defining a class is

  public class TypeName  extends OtherType   
     private Type1 AttributeList1 ;
     private Type2 AttributeList2 ;
     private TypeN AttributeListN ;

     public TypeName1 MethodDef1 
     public TypeName2 MethodDef2 
     public TypeNameM MethodDefM   

All of the declarations that follow the keyword public can be accessed by programs using the class; and all of the declarations that follow the keyword private cannot be accessed by programs using the class.

By convention, all of the attributes of an object are grouped together.


Methods As Messages

We have seen that these methods are called differently from the static methods we have used earlier: if two String objects named greeting and closing are defined as follows:

   String greeting = "hello",
          closing = "goodbye";

then the expression


returns the value 5, while the expression


returns the value 7.

Object-oriented programmers like to think of a call to a method as a message to which the object responds. To illustrate, when we send the size() message to greeting, greeting responds with the number of characters it contains (5); and if we send the same size() message to closing, closing responds with the number of characters it contains (7). The effect of this approach is to shift the point of view from the method to the object.

In contrast, the static methods we used before perform are sent to the class itself not to an object of that class. Just as we can have static methods, we can have static attributes. Static attributes have just one copy and are not stored in each object of that class. Those attributes are effectively shared by all objects of the class are sometimes called class wide. While it is easy to access a static method or attribute from an objects regular methods, the reverse is not true.

Put differently, regular methods are usually written from the perspective of the class object and must have an object to work with. A static method performs its duties independently of the objects of a class and can work even if no object of that class exists.


Part I.

In this first part of today's exercise, we will focus on adding the methods to class Fraction needed to get operational.


An Output Method

To facilitate debugging a class, it is often a good idea to begin with a method that can be used to display class objects -- an output method.

From the perspective of our Coordinate class, we can specify the task of such a method as follows:

  Receive: out, a Screen to which I am to write my values.
  Output: myX and myY, as a pair.

To define print(), we must put the code into the class definition as follows:

   public void print(Screen out)
      out.print( "(" + myX + "," + myY + ")" );

As explained earlier

Note also that the prefix my helps to reinforce the notion that this is a message to which a Coordinate object responds. That is, if point is a Coordinate object whose X-value is 3 and whose Y-value is 4, then the statement


displays via theScreen:


Similarly, if origin is a Coordinate object whose X-value is 0 and whose Y-value is 0, then the statement


displays via theScreen:


Note that as a message to an object, a method must be invoked using dot notation, which specifies the object to which the message is being sent.

Note finally that as a message to which an object responds, a method can directly access the private attributes of the object.

Using this information as a pattern, define a similar print() method for your Fraction class, such that if oldMeasure is a Fraction whose numerator is 3 and whose denominator is 4, then a message:


will display


Check the syntax of your method, and continue when it is correct.


The Class Constructor

An output operation for a class is of little use unless we are able to define and initialize objects of that class. The action of defining and initializing an object is called constructing that object. To allow the designer of a class to control the construction of class objects, Java allows us to define a method called a class constructor that specifies exactly what actions are to be taken when a class object is created. When a class object is defined, the Java compiler calls this method to initialize the object's attributes.

For example, suppose we would like for a definition of a Coordinate object:

   Coordinate point = new Coordinate();

to initialize the attributes of the object to which point refers to zeros. We might specify this task as follows:

   Postcondition: myX == 0.0 && myY == 0.0.

Note that a constructor does not return anything to its caller, it simply initializes the attributes of an object when that object is defined. We specify this behavior through a boolean expression that is true when the method terminates. Such an expression is called a postcondition, since it is a condition that holds when execution reaches the end of the method.

The name of a constructor is always the name of the class, so we define this method as public in the class Coordinate, as follows:

   public Coordinate()
     myX = 0.0;
     myY = 0.0;

Given this method, when a Coordinate object is defined, the Java compiler will automatically call this code to initialize this object, which sets the object's myX and myY attributes to zero values.

Note that a constructor has no return type (not even void). As was mentioned earlier, this is because a constructor never returns anything to its caller -- it merely initializes objects of its class.

The pattern for a constructor is thus:

   public ClassName(ParameterList)

where the ClassName refers to the name of the class and StatementList is a sequence of statements that initialize the attributes of the class. Constructors can take parameters, which are defined as they would be for any other method, and any valid Java statement can appear in the body.

Using this information, define a constructor for your Fraction class, that satisfies the following specification:

   Postcondition: myNumerator == 0 && myDenominator == 1.

That is, the definition:

   Fraction oldMeasure = new Fraction();

should initialize the attributes of oldMeasure appropriately to represent the fraction 0/1.

Constructors are only invoked by new. For example, consider the following seemingly legal code:

  Fraction oldMeasure;

The first line does not create an object of type Fraction. The variable oldMeasure has been declared and given the special value null. If we send a message to a null object, an error will result. The java compiler will recognize this and generate a compilation error, to the effect that oldMeasure has not been initialized. This is a very common kind of error that occurs in Java. You are encouraged to try this code and see the error message that results so you will recognize it when you see it again.

Test the syntax of what you have written, and continue when it is correct.


A Second Constructor

A class can have multiple constructors, so long as each definition is distinct in either the number or the type of its parameters. Defining the same method multiple times is called overloading the method.

To illustrate, suppose that we would like to be able to explicitly initialize the X-value and Y-value of a Coordinate object to two values that are specified when the object is defined. We might specify this task as follows:

  Receive: x and y, two double values.
  Postcondition: myX == x &&
                 myY == y.

We can perform this task by overloading the Coordinate constructor with another definition that takes two double arguments and uses them to initialize our attributes:

   public Coordinate(double x, double y)
     myX = x;
     myY = y;

Given such a method, the Java compiler will process a Coordinate declaration statement like this:

   Coordinate point1 = new Coordinate(),
              point2 = new Coordinate(1.2, 3.4);

by using our first constructor to initialize point1 (since it has no arguments), and use our second constructor to initialize point2 (since it has two arguments), resulting in objects that we might visualize as follows:

Using this information, define and prototype a second Fraction constructor that satisfies this specification:

   Receive: numerator and denominator, two integers.
   Precondition: denominator != 0.
   Postcondition: myNumerator == numerator &&
                  myDenominiator == denominator.

That is, the definitions:

   Fraction oldMeasure = new Fraction();
   Fraction scaleFactor = new Fraction(1, 6);

should initialize oldMeasure to 0/1, and initialize scaleFactor to 1/6. Be sure that your explicit-value constructor checks its precondition (e.g., use the hoj.Assert class).

Use the Java compiler to test the syntax of what you have written. When the syntax is correct, use to test what you have done, by inserting calls to print() to display their values:


When your methods are working correctly, remove this "test code" from


Accessor Methods

Any operations we wish to perform on a class object can be defined as public methods within the class. Accessors are methods that retrieve an attribute of the object. These are often simple one line methods. For example, it might be useful to be able to extract the x and y values of a Coordinate object. The first task can be specified as:

   Return: my X-value.

while the second is

   Return: my Y-value.

We would then define these simple methods in Coordinate, as follows:

   public double getX()
      return myX;

   public double getY()
      return myY;

Given such methods, if two Coordinate objects point1 and point2 are as follows:

then the expression:


evaluates to 0.1, while the expression


evaluates to 8.9.

We should mention that it is Java's convention to give accessor methods the name getA() where A is the particular attribute we are retrieving. Thus, if we have used the name myA for our attribute, getA() is the name of the corresponding method to extracts the value of that attribute. For this reason, accessors are sometimes called getters by Java programmers. (Correspondingly, mutators methods that change an attribute A's value are named setA(), and are thus called setters.)

Using this information, add to class Fraction an accessor method getNumerator() that satisfies this specification:

   Return: myNumerator.

and an accessor method getDenominator() that satisfies this specification:

   Return: myDenominator.

Then test their syntax and continue when they are correct.



Once we are able to define Fraction objects, it is useful to be able to input a Fraction value. To illustrate, suppose that we wanted to input a Coordinate value:


We can specify the problem as follows:

   Receive: in, a Keyboard.
   Precondition: in contains a Coordinate of the form (x,y).
   Input: (x,y), from in.
   Postcondition: myX == x && myY == y.

To solve the problem, we can define read() as a method that satisfies the specification, as follows:

   public void read(Keyboard in)
      String point = in.readWord();
      StringTokenizer parser = new StringTokenizer(point, "(,)", false);

      if(parser.countTokens() != 2)
          throw new RuntimeException("bad format for coordinate");

      double x = Double.parseDouble(parser.nextToken());
      double y = Double.parseDouble(parser.nextToken());
      myX = x;
      myY = y;

Since we are reading the values entered by a (fallible) person, there are a number of things that could go wrong here:

To help us deal with these problems, we can read what the user enters as a String, break that String down into its constituent parts, and then convert those parts into double values.

To read what the user enters, we use the readWord() (or readLine()) methods from ann.easyio.Keyboard. To break the resulting String into its constituent parts, we can use Java's predefined StringTokenizer class::

      StringTokenizer parser = new StringTokenizer(point, "(,)", false);
This statement breaks the String named point into pieces called substrings, using the open-parenthesis, comma, and close-parenthesis symbols to separate the pieces. To illustrate, if the user enters (1.0,3.5), then our StringTokenizer will break the String "(1.0,3.5)" into "1.0" and "3.5". We can then retrieve these two parts by sending our StringTokenizer the nextToken() message twice, as shown above. (see GUI Interlude #2 for more information about it) that is stored in the package java.util, so to use it, you should add this line before the beginning of your Fraction class in
   import java.util.StringTokenizer;
Once we have the string broken into its pieces, we need to convert those pieces to double values. We can do so using the parseDouble() method from the Double class, as shown above. If the user enters a non-numeric value (e.g., (one,three)), the parseDouble() method throws an exception. To avoid "corrupting" our instance variables myX and myY, we store the values returned by parseDouble() in local variables x and y, and when both parseDouble() calls have succeeded, we update our instance variables.

Given such a definition for read(), the following code:

   Coordinate point = new Coordinate();;

would read a Coordinate of the form (x,y) from theKeyboard.

Using this information, define an input method named read() for class Fraction. Your method should satisfy this specification:

   Receive: in, an Keyboard.
   Precondition: in contains a Fraction value of the form n/d,
                   such that d != 0.
   Input: n/d, from in.
   Postcondition: myNumerator == n && 
                  myDenominator == d.
Note that since a Fraction stores int values (as opposed to the double values our Coordinate class stores), your method will need to use Integer.parseInt() method instead of Double.parseDouble().

When finished, you should be able to "uncomment" the statements:;

and read a Fraction value from theKeyboard into oldMeasure and scaleFactor. Put differently, we should be able to send a Fraction object the read() message, with the Keyboard from which it should read as an argument.

Test your input method by adding statements like those above to, along with a print() that echos the input values back to the screen. Compile and run the program, and continue when read() works correctly.


Fractional Multiplication

We have seen that methods like constructors can be overloaded. Unfortunately, one of the design decisions of Java was not to allow operators, such as the arithmetic operators (+, -, *, /, and %) to be overloaded. We will need to define methods for those operations.

As an illustration, suppose that we want to permit two Coordinate objects to be added together. In the object-oriented world, an expression like


is thought of as sending the add message to point1, with point2 as a message argument. That is, we can specify the problem from the perspective of the Coordinate receiving this message as follows:

   Receive: point2, a Coordinate.
   Return: result, a Coordinate.
   Postcondition: result.myX == myX + point2.myX &&
                  result.myY == myY + point2.myY.

One way to define this method is as follows:

   public Coordinate add(Coordinate point2)
      Coordinate result = 
          new Coordinate(myX + point2.getX(), myY + point2.getY());
      return result;

This definition uses our second constructor to construct and initialize result with the appropriate values.

While it is useful to define all of the arithmetic methods for a Fraction, the particular operation that we need in order to solve our problem is multiplication (the others, we leave for the exercises). From the preceding discussion, it should be evident that we need to define times so that the expression in


can be used to multiply the two Fraction objects oldMeasure and scaleFactor. We can get some insight into the problem by working some simple examples:

   1/2  * 2/3  = 2/6  = 1/3               3/4  * 2/3  = 6/12  = 1/2 

The specification for such an operation can be written as follows:

   Receive: rightOperand, a Fraction operand.
   Return: result, a Fraction, containing the product of
             the receiver of this message and rightOperand,
             simplified, if necessary.

From these examples, it should be apparent that we can construct result by taking the product of the corresponding attributes and then simplifying the resulting Fraction.

For the moment, let's ignore the problem of simplifying an improper Fraction. Extend your Fraction class with a definition of times that can be used to multiply two Fraction objects. Then test the correctness of what you have written by "uncommenting" the lines in that that compute and output newMeasure. Continue when your multiplication operation yields correct, (if unsimplified) results.

Fraction Simplification. The main deficiency of our implementation of times is its inability to simplify improper fractions. That is, our multiplication operation would be improved if class Fraction had a simplify operation, such that fractions like:

   2/6          6/12           12/4  

could be simplified to:

   1/3          1/2            3/1

respectively. Such an operation is useful to keep fractional results as simple and easy to read as possible. To provide this capability, we will implement a Fraction method named simplify(), such that a method like times can call it as shown in the following code:

   public Fraction times(Fraction right)
      // compute result...


      return result;

There are a number of ways to simplify a fraction. One straightforward way is the following algorithm:

      0. Find gcd, the greatest common divisor of 
           myNumerator and myDenominator.
      1. If gcd = 0, then terminate
      2. Replace myNumerator by myNumerator/gcd.
      3. Replace myDenominator by myDenominator/gcd.

Notice that after this method has finished, the state of the object may have changed ( myNumerator and myDenominator may be different.) A method that changes the state of the object is called a mutator.

The implementation file contains a method greatestCommonDivisor() that implements Euclid's algorithm for finding the greatest common divisor of two integers. Notice that it is a static method inside our class, so we can just call it directly (i.e., just do greatestCommonDivisor()). Using method greatestCommonDivisor() and the preceding algorithm, define method simplify() as a method of class Fraction.

We have now provided all of the operations needed by, so the complete program should be operable and can be used to test the operations of our class.


Part II.

In this second part of today's exercise, we add the functionality to class Fraction in order for to work properly.

If you are using an IDE/project, remove from it. Then add

Open for editing.


Output Revisited

While we have provided the capability to output a Fraction value via a print() method, doing so requires that we write clumsy code like:

    theScreen.print("\nThe converted measurement is: ");

instead of more elegant code like:

   theScreen.println("\nThe converted measurement is: "
          + newMeasure);

Put differently, our print() method solves the problem, but it doesn't fit in particularly well with the rest of Java's output operations. It would be preferable if we could use the print and println methods to display a Fraction value.

To do so we need to understand a little bit about println. The statement

   Coordinate someWhere = new Coordinate(4,3);

is equivalent to

   Coordinate someWhere = new Coordinate(4,3);
   String aString = someWhere.toString();

In other words, Java assumes that your object understands the toString() message and will return an appropriately defined String. (Via the mechanism of inheritance, which we will discuss later, all classes understand the toString() message, but what is returned is not usually appropriate for friendly output.)

To do this, we would define the following method inside the class Coordinate.

   public String toString()
      return "(" + myX + "," + myY + ")";

Consider that with this new method we now have two methods that do almost the same job.

   public String toString()
      return "(" + myX + "," + myY + ")";

   public void print(Screen out)
      out.print( "(" + myX + "," + myY + ")" );

While this code is not incorrect, it can present a maintenance problem. Suppose that some time in the future we wish to change the format for printing a coordinate. For example, we might want to use square brackets instead of parentheses. To remain consistent, we must change both methods. For a small class like Coordinate this might not seem like a big problem, but as classes expand it is easy to miss making a change. To fix this, we should redefine one of these methods in terms of the other.

This is a general software design technique that helps improve ones ability to write and maintain code. If we have two chunks of code that do the same job, factor that code into a separate method. While it was not obvious now, that is the reason we wrote a simplify() method. Other methods besides times() will want to provide results in simplified form. By doing it just once, we are less likely to make a mistake. If we have made a mistake, we only need to fix the code in one easily located place.

The natural way to factor out the common expression in our previous example is:

   public String toString()
      return "(" + myX + "," + myY + ")";

   public void print(Screen out)


In the print method this refers to the object that the print message was sent to. So the toString() message is sent to that same object and then the string that is returned will be printed. Now there is only one piece of code that would need to change if we wanted to change the format.

Using this information, add a toString() method to the class Fraction, so that if the value of newMeasure is 1/2 , then

   theScreen.println("\nThe converted measurement is: " + newMeasure);

will cause

   The converted measurement is: 1/2

to be displayed on the screen. Test what you have written and continue when it is correct. Remember to use good design and factor out the common code.


Object-Centered Design Revisited

Now that you have seen how to build a class, we need to expand our design methodology to incorporate classes:

  1. Describe the behavior of the program.

  2. Identify the objects in the problem:
       If an object cannot be directly represented using available types:
         Design and implement a class to represent such objects.

  3. Identify the operations needed to solve the problem:
       If an operation is not predefined:
         1) Design and implement a method to perform that operation.
         2) If the operation is a message that will be sent to an object:
                 Design and implement the method as non-static
            Otherwise (the operation is a message that will be sent to a class):
                 Design and implement the method as static

  4. Organize the objects and operations into an algorithm.

Using this methodology and the Java class mechanism, we can now create a software model of any object! The class thus provides the foundation for object-oriented programming, and mastering the use of classes is essential for anyone wishing to program in the object-oriented world.

Learning to design and implement classes is an acquired skill, so feel free to practice by creating software models of objects you see in the world around you!


Phrases you should now understand:

Class, Attribute, Method, Precondition, Postcondition, Constructor, Accessor, Mutator, Overloading, Factoring code, toString().



Hard copies of your final version of,,, and an execution record showing the execution of each program.

Back to This Lab's Table of Contents

Back to the Prelab Questions

Forward to the Homework Projects

Back to the Table of Contents

Back to the Introduction

Copyright 2000 by Prentice Hall. All rights reserved.