CHAPTER 1

Object-Oriented Concepts

CHAPTER OBJECTIVES

  1. Compare a user's view of a class with a developer's view of that class.
  2. Understand how inheritance promotes code re-use.
  3. Understand how polymorphic references can be useful.
  4. Be able to create class diagrams in the Unified Modeling Language.

1.1 Data Abstraction

A user of a class concentrates on the class's method specifications, that is, what a class provides. A developer of a class, on the other hand, focuses on the class's fields and method definitions, that is, how the class is defined. This separation—called data abstraction —of what from how is an essential feature of object-oriented programming. For example, programmers who use the String class will not care about the fields that represent a string or how the methods are defined. Such details would be of no help when you are trying to develop a class that uses the string class, but were essential to the developers of the String class.

In general, suppose you are a programmer who is developing class A, and during development, you decide that you will need the services of class B. If someone else has already completed the definition of class B, you should simply use that class rather than re-inventing the wheel. But even if you must define the class, B yourself, you can simply create the method specifications for class B and postpone any further work on class B until after you have completed the development of class A. By working with class B's method specifications, you increase the independence of class A: its effectiveness will not be affected by any changes to class B that do not affect the method specifications of class B.

When users focus on what a class provides to users rather than on the implementation details of that class, they are applying the Principle of Data Abstraction: A user's code should not access the implementation details of the class used.

One important application of the Principle of Data Abstraction is that if class A simply uses class B, then class A's methods should not access class B's fields. In fact, class B's fields should be accessed only in class B's method definitions. This turns out to be a benefit to users of class B because they will be unaffected if the developer of class B decides to replace the old fields with new ones. For example, suppose the following definition is made outside of the string class:

string name;

Currently, one of the fields in the string class is an int field named count. But an expression such as

name.count

would be a violation of the Principle of Data Abstraction because whether or not the string class has a count field is an implementation detail. The developer of the string class is free to make any changes to the string class that do not affect the method specifications.

Most programming languages, including Java, have features that enable a developer of a class to force users to adhere to the Principle of Data Abstraction. These enforcement features are collectively known as "information hiding." We will discuss information hiding in Section 1.5 after we have introduced several of the relevant features.

We noted earlier that the Principle of Data Abstraction is a benefit to users of a class because they are freed from reliance on implementation details of that class. This assumes, of course, that the class's method specifications provide all of the information that a user of that class needs. The developer of a class should create methods with sufficient functionality that users need not rely on any implementation details. That functionality should be clearly spelled out in the method specifications. In particular, the user should be told under what circumstances a method call will be legal, and what the effect of a legal call will be.

In a method specification, the first sentence in a javadoc comment block is called the postcondition: the effect of a legal call to the method. The information relating to the legality of a method call is known as the precondition of the method. The precondition may be stated explicitly within or after the postcondition (for example, "The array must be sorted prior to making this call.") or implicitly from the exception information (for example, any call that throws an exception is illegal). The interplay between a method's precondition and postcondition determines a contract between the developer and the user. The terms of the contract are as follows:

If the user of the method ensures that the precondition is true before the method is invoked, the developer guarantees that the postcondition will be true at the end of the execution of the method.

We can summarize our discussion of classes so far by saying that from the developer's perspective, a class consists of fields and the definitions of methods that act on those fields. A user's view is an abstraction of this: A class consists of method specifications.

The Java Collections Framework is, basically, a hierarchy of thoroughly tested classes that are useful in a variety of applications. The programs in this book will use the Java Collections Framework, so those programs will not rely on the definitions of the framework's methods. We will provide method specifications and an overview of most of the classes. Occasionally, to give you experience in reading the code of professional programmers, you will get to study the fields and some of the method definitions.

In Section 1.2, we see the value of classes that have undefined methods.

1.2 Abstract Methods and Interfaces

Up to now, we have used method specifications for the purpose of promoting data abstraction. That is, a user should focus on the method specifications in a class and ignore the class's fields and method definitions. Some methods don't even have a definition, and it turns out that this can be helpful to programmers. For example, suppose we want to create classes for circles, rectangles and other figures. In each class, there will be methods to draw the figure and to move the figure from one place on the screen to another place on the screen. The Circle class, for example, will have a draw method and a move method based on the center of the circle and its radius. Here are two method specifications and related constant identifiers that will apply to all of the figure classes:

final static int MAX_X_COORD = 1024;

final static int MAX_Y_COORD = 7 68;

/**
 * Draws this Figure object centered at the given coordinates.
 *

 * @param x - the X coordinate of the center point of where this Figure object
 *            will be drawn.
 * @param y - the Y coordinate of the center point of where this Figure object
 *            will be drawn.
 *
 */
public void draw(int x,   int y)


/**
 *  Moves this Figure object to a position whose center coordinates are specified.
 *
 *  @param x - the X coordinate of the center point of where this Figure object
 *             will be moved to.
 *  @param y - the Y coordinate of the center point of where this Figure object
 *             will be moved to.
 *
 */
public void move  ( int x,   int y)

Each different type of figure will have to provide its own definitions for the draw and move methods. But by requiring that those definitions adhere to the above specifications, we introduce a consistency to any application that uses the figure classes. A user of one of those classes knows the exact format for the draw and move methods—and that will still be true for classes corresponding to new figure-types.

Java provides a way to enforce this consistency: the interface. Each method heading is followed by a semicolon instead of a definition. Such a method is called an abstract method. An interface is a collection of abstract methods and constants. There are no defined methods and no fields. For example, here is the interface for figures:

public interface Figure
{
       final static int MAX_X_COORD = 1024;


       final static int MAX_Y_COORD = 768;


       /**
        * Draws this Figure object centered at the given coordinates.
        *
        *  @param x - the X coordinate of the center point of where this Figure
        *       object will be drawn.
        *  @param y - the Y coordinate of the center point of where this Figure
        *             object will be drawn.
        *
        */
       void draw(int x, int y);


       /**
        * Moves this Figure object to a position whose center coordinates are
        * specified.
        *

        *  @param x - the X coordinate of the center point of where this Figure
        *             object will be moved to.
        *  @param y - the Y coordinate of the center point of where this Figure
        *             object will be moved to.
        *
        */
        void move (int x, int y);


} // interface Figure

The interface Figure has two constants (MAX_X_COORD and MAX_Y_COORD) and two abstract methods, (draw and move). In any interface, all of the method identifiers and constant identifiers are public, so the declarations need not include the visibility modifier public.

When a class provides method definitions for an interface's methods, the class is said to implement the interface. The class may also define other methods. For example, here is part of a declaration of the Circle class:

 public class Circle implements Figure
 {
       // declaration of fields:
       private int xCoord,
                  yCoord,
                  radius;


       // constructors to initialize x, y and radius:
       ...
       /** (javadoc comments as above)
       */
       public draw (int x, int y)
       {
             xCoord = x;
             yCoord = y;


            // draw circle with center at (xCoord, yCoord) and radius:
             ...
       } // method draw


      // definitions for move and any other methods:
       ...


} // class Circle

The reserved word implements signals that class Circle provides method definitions for the methods whose specifications are in the interface Figure. Interfaces do not include constructors because constructors are always class specific. The incompleteness of interfaces makes them uninstantiable, that is, we cannot create an instance of a Figure object. For example, the following is illegal:

Figure myFig = new Figure();  // illegal

In the method specifications in the Figure interface, the phrase "this Figure object" means "this object in a class that implements the Figure interface."

Of what value is an interface? In general, an interface provides a common base whose method specifications are available to any implementing class. Thus, an interface raises the comfort level of users because they know that the specifications of any method in the interface will be adhered to in any class that implements the interface. In practice, once a user has seen an interface, the user knows a lot about any implementing class. Unlike the interface, the implementing class will have constructors, and may define other methods in addition to those specified in the interface.

1.2.1 Abstract Data Types and Data Structures

An abstract data type consists of a collection of values, together with a collection of operations on those values. In object-oriented languages such as Java, abstract data types correspond to interfaces in the sense that, for any class that implements the interface, a user of that class can:

  1. create an instance of that class; ("instance" corresponds to " value")
  2. invoke the public methods of that class ("public method" corresponds to "operation").

A data structure is the implementation of an abstract data type. In object-oriented languages, a developer implements an interface with a class. In other words, we have the following associations:

general term object-oriented term
abstract data type Interface
data structure class

A user is interested in abstract data types—interfaces—and a class's method specifications, while a developer focuses on data structures, namely, a class's fields and method definitions. For example, one of the Java Collections Framework's interfaces is the List interface; one of the classes that implement that interface is LinkedList. When we work with the List interface or the LinkedList method specifications, we are taking a user's view. But when we consider a specific choice of fields and method definitions in LinkedList, we are taking a developer's view.

In Chapter 0, we viewed the string class from the user's perspective: what information about the string class is needed by users of that class? A user of a class writes code that includes an instance of that class. Someone who simply executes a program that includes an instance of a class is called an end-user. A developer of a class actually creates fields and method definitions. In Section 1.2.2, we will look at the developer's perspective and compare the user's and developer's perspectives. Specifically, we create an interface, and utilize it and its implementing classes as vehicles for introducing several object-oriented concepts.

1.2.2 An Interface and a Class that Implements the Interface

This section is part of an extended example that continues through the rest of the chapter and illustrates several important object-oriented concepts. Let's create a simple interface called Employee for the employees in a company. The information for each employee consists of the employee's name and gross weekly pay. To lead into the method specifications, we first list the responsibilities of the interface, that is, the services provided to users of any class that implements the Employee interface. The responsibilities of the Employee interface are:

  1. to return the employee's name;
  2. to return the employee's gross pay;
  3. to return a string representation of an employee.

These responsibilities are refined into the following interface:

import java.text.DecimalFormat;


public interface Employee
{


       final static DecimalFormat MONEY = new DecimalFormat (" $0.00");
             // a class constant used in formatting a value in dollars and cents


      /**
       *  Returns this Employee object's name.
       *
       *  @return this Employee object's name.
       *
       */
       String getName();



      /**
       *  Returns this Employee object's gross pay.
       *
       *  @return this Employee object's gross pay.
       *
       */
       double getGrossPay();



      /**
       * Returns a String representation of this Employee object with the name
       * followed by a space followed by a dollar sign followed by the gross
       * weekly pay, with two fractional digits (rounded).
       *
       * @return a String representation of this Employee object.
       *
       */
       String toString();


} // interface Employee

The identifier MONEY is a constant identifier—indicated by the reserved word final. The reason for declaring a MONEY object is to facilitate the conversion of a double value such as gross pay into a string in dollars-and-cents format suitable for printing. Instead of having a separate copy of the MONEY object for each instance of each class that implements the Employee interface, there is just one MONEY object shared by all instances of implementing classes. This sharing is indicated by the reserved word static.

The phrase "this Employee object" means "the calling object in a class that implements the Employee interface."

The Employee interface's method specifications are all that a user of any implementing class will need in order to invoke those methods. A developer of the class, on the other hand, must decide what fields to have and then define the methods. A convenient categorization of employees is full-time and part-time. Let's develop a FullTimeEmployee implementation of the Employee interface.

For example, a developer may well decide to have two fields: name (a string reference) and grossPay (a double). The complete method definitions are developed from the fields and method specifications. For example, here is a complete declaration of the FullTimeEmployee class; the next few sections of this chapter will investigate various aspects of the declaration.

import java.text.DecimalFormat;


public class FullTimeEmployee implements Employee
{
  private String name;


  private double grossPay;



  /**
  * Initializes this FullTimeEmployee object to have an empty string for the
  *  name and 0.00 for the gross pay.
  *
  */
  public FullTimeEmployee()
  {
     final String EMPTY_STRING = "";


     name = EMPTY_STRING;
     grossPay = 0.00;
  } // default constructor



  /**
   * Initializes this FullTimeEmployee object's name and gross pay from a
   *  a specified name and gross pay.
   *
   * @param name - the specified name.
   * @param grossPay - the specified gross pay.
   *
   */
  public FullTimeEmployee (String name, double grossPay)
  {
     this .name = name;
     this.grossPay = grossPay;
  } // 2-parameter constructor



  /**
   *   Returns the name of this FullTimeEmployee object.
   *
   * @return the name of this FullTimeEmployee object.
   *
   */
  public String getName()
  {

     return name;
  } // method getName



  /**
   *   Returns the gross pay of this FullTimeEmployee object.
   *
   * @return the gross pay of this FullTimeEmployee object.
   *
   */
  public double getGrossPay()
  {
      return grossPay;
  } // method getGrossPay



  /**
   *  Returns a String representation of this FullTimeEmployee object with the
   *  name followed by a space followed by a dollar sign followed by the gross
   * weekly pay, with two fractional digits (rounded), followed by "FULL TIME".
   *
   * @return a String representation of this FullTimeEmployee object.
   *
   */
  public String toString()
  {
      final String EMPLOYMENT_STATUS = "FULL TIME";


      return name + MONEY.format (grossPay) + EMPLOYMENT_STATUS;
            // the format method returns a String representation of grossPay.
  } // method toString


} // class FullTimeEmployee

In the two-parameter constructor, one of the parameters is name. The reserved word this is used to distinguish between the scope of the field identifier name and the scope of the parameter name. In any class, the reserved word this is a reference to the calling object, so this.name refers to the name field of the calling object, and name by itself refers to the parameter name. Similarly, this. grossPay refers to the calling object's grossPay field, and grossPay by itself refers to the parameter grossPay.

In the other methods, such as toString(), there is no grossPay parameter. Then the appearance of the identifier grossPay in the body of the toString() method refers to the grossPay field of the object that called the toString() method.

The same rule applies if a method identifier appears without a calling object in the body of a method. For example, here is the definition of the hasNextLine() method in the Scanner class:

public boolean hasNextLine()
{

      saveState();
      String result = findWithinHorizon(
                      ".*("+LINE_SEPARATOR_PATTERN+")|.+$",   0);

     revertstate();
     return  (result  != null);
}

There is no calling-object reference specified for the invocation of the savestate() method, and so the object assumed to be invoking savestate() is the object that called hasNextLine(). Similarly, the methods findWithinHorizon and revertstate are being called by that same object. Here is the general rule, where a member is either a field or a method:

If an object has called a method and a member appears without an object reference in the method definition, the member is part of the calling object.

As you can see from the declaration of the FullTimeEmployee class, in each method definition there is at least one field that appears without an object reference. In fact, in almost every method definition in almost every class you will see in subsequent chapters, there will be at least one field that appears without an object (reference). Then the field is part of the calling object.

1.2.3 Using the FullTimeEmployee Class

As an example of how to use the FullTimeEmployee class, we can find the best-paid of the full-time employees in a company. The information for each employee will be on one line in a file, and the name of the file will be scanned in from System.in.

For convenience, the following Company class includes a main method. For the sake of an object orientation, that main method simply invokes the Company class's run method on a newly constructed Company instance; all of the main methods from here on will be one-liners. The run method calls a findBestPaid method to return the best-paid full-time employee, or null if there were no employees scanned in. Finally, the findBestPaid method invokes a getNextEmployee method to handle the details of constructing a FullTimeEmployee instance from a name and a gross pay.

Here is the complete program file, with three scanner objects, one to scan the name of the file of employees, one to scan over that file, and one to scan a line in that file:

import java.util.*; // for the Scanner class


import java.io.*; // for the FileNotFoundException class ' see Section 2.3


public class Company
{
   public static void main (String[ ] args) throws FileNotFoundException
   {
      new Company().run();
   } // method main


   /**
   *  Determines and prints out the best paid of the full-time employees
   * scanned in from a specified file.
   *
   */
   public void run() throws FileNotFoundException // see Section 2.3

{
   final String INPUT_PROMPT = "Please enter the path for the file of employees: ";
   final String BEST_PAID_MESSAGE =
      "
 nThe best-paid employee (and gross pay) is ";

   final String NO_INPUT_MESSAGE =
      "
 nError: There were no employees scanned in.";

   String fileName;


   System.out.print (INPUT_PROMPT);
   fileName = new Scanner (System.in).nextLine();
   Scanner sc = new Scanner (new File (fileName));


   FullTimeEmployee bestPaid = findBestPaid (sc);


   if (bestPaid == null)
       System.out.println (NO_INPUT_MESSAGE);
   else
       System.out.println (BEST_PAID_MESSAGE + bestPaid.toString());
} // method run



/**
*  Returns the best paid of all the full-time employees scanned in.
*
* @param sc - the Scanner object used to scan in the employees.
*
*  @return the best paid of all the full-time employees scanned in,
*          or null there were no employees scanned in.
*
*/
public FullTimeEmployee findBestPaid (Scanner sc)
{
   FullTimeEmployee full,
                    bestPaid = new FullTimeEmployee();


   while (sc.hasNext())
   {
       full = getNextEmployee (sc);
       if (full.getGrossPay() > bestPaid.getGrossPay())
           bestPaid = full;
   } //while
   if (bestPaid.getGrossPay() == 0.00)
        return null;
   return bestPaid;
} // method findBestPaid


/**
 * Returns the next full-time employee from the file scanned by a specified Scanner

    * object.
    *
    * @param sc - the Scanner object over the file.
    *
    * @return the next full-time employee scanned in from sc.
    *
    */
   private FullTimeEmployee getNextEmployee (Scanner sc)
   {
      Scanner lineScanner = new Scanner (sc.nextLine());


      String name = lineScanner.next();


      double grossPay = lineScanner.nextDouble();


      return new FullTimeEmployee (name, grossPay);
   } // method getNextEmployee


} // class Company

The above code is available from the Chapter 1 directory of the book's website. If the file name scanned in from System.in is "full.inl", and the corresponding file contains

a 1000.00
b 3000.00
c 2000.00

then the output will be

The best-paid employee (and gross pay) is b $3000.00  FULL TIME

As noted earlier, we should use existing classes whenever possible. What if a class has most, but not all, of what is needed for an application? We could simply scrap the existing class and develop our own, but that would be time consuming and inefficient. Another option is to copy the needed parts of the existing class and incorporate those parts into a new class that we develop. The danger with that option is that those parts may be incorrect or inefficient. If the developer of the original class replaces the incorrect or inefficient code, our class would still be erroneous or inefficient. A better alternative is to use inheritance, explained in Section 1.3.

1.3 Inheritance

We should write program components that are reusable. For example, instead of defining a method that calculates the average gross pay of 10 employees, we would achieve wider applicability by defining a method that calculates the average gross pay of any number of employees. By writing reusable code, we not only save time, but we also avoid the risk of incorrectly modifying the existing code.

One way that reusability can be applied to classes is through a special and powerful property of classes: inheritance. Inheritance is the ability to define a new class that includes all of the fields and some or all of the methods of an existing class. The previously existing class is called the superclass. The new class, which may declare new fields and methods, is called the subclass. A subclass may also override existing methods by giving them method definitions that differ from those in the superclass.1 The subclass is said to extend the superclass.

For an example of how inheritance works, let's start with the class FullTimeEmployee defined in Section 1.2.2. Suppose that several applications use FullTimeEmployee. A new application involves finding the best-paid, full-time hourly employee. For this application, the information on an hourly employee consists of the employee's name, hours worked (an int value) and pay rate (a double value). Assume that each employee gets time-and-a-half for overtime (over 40 hours in a week). If the hourly employee did not work any overtime, the gross pay is the hours worked times the pay rate. Otherwise, the gross pay is 40 times the pay rate, plus the overtime hours times the pay rate times 1.5.

We could alter FullTimeEmployee by adding hoursWorked and payRate fields and modifying the methods. But it is risky to modify, for the sake of a new application, a class that is being used successfully in existing applications. The underlying concept is known as the Open-Closed Principle: Every class should be both open (extendible through inheritance) and closed (stable for existing applications).

Instead of rewriting FullTimeEmployee, we will create HourlyEmployee, a subclass of FullTimeEmployee. To indicate that a class is a subclass of another class, the subclass identifier is immediately followed by the reserved word extends. For example, we can declare the HourlyEmployee class to be a subclass of FullTimeEmployee as follows:

public class HourlyEmployee extends FullTimeEmployee
{
      ...

Each HourlyEmployee object will have the information from FullTimeEmployee—name and gross pay—as well as hours worked and pay rate. These latter two will be fields in the HourlyEmployee class. To lead us into a discussion of the relationship between the FullTimeEmployee fields and the HourlyEmployee fields, here is a constructor to initialize an HourlyEmployee instance from a name, hours worked, and pay rate (MAX_REGULAR_HOURS is a constant identifier with a current value of 40, and OVERTIME_FACTOR is a constant identifier with a current value of 1.5).

/**
* Initializes this full-time HourlyEmployee object's name, hours worked, pay rate, and
* gross pay from a a specified name, hours worked and pay rate. If the hours worked
*  is at most MAX_REGULAR_HOURS, the gross pay is the hours worked times
*  the pay rate. Otherwise, the gross pay is MAX_REGULAR_HOURS times the
* pay rate, plus the pay rate times OVERTIME_FACTOR for all overtime hours.
*
* @param name - the specified name.
* @param hoursWorked - the specified hours worked.
* @param payRate - the specified pay rate.
*
*/
public HourlyEmployee (String name, int hoursWorked, double payRate)
{
   this.name = name;
   this.hoursWorked = hoursWorked;
   this.payRate = payRate;

   if (hoursWorked <= MAX_REGULAR_HOURS)
   {
       regularPay = hoursWorked * payRate;
       overtimePay = 0.00;
   } // if
   else
   {
       regularPay = MAX_REGULAR_HOURS * payRate;
       overtimePay = (hoursWorked - MAX_REGULAR_HOURS) *
                     (payRate * OVERTIME_FACTOR);
   } // else
   grossPay = regularPay + overtimePay;
} // 3-parameter constructor

Notice that in the definition of this 3-parameter constructor for HourlyEmployee, the name and grossPay fields from the FullTimeEmployee class are treated as if they had been declared as fields in the HourlyEmployee class. The justification for this treatment is that an HourlyEmployee object is also a FullTimeEmployee object, so every FullTimeEmployee field is also an HourlyEmployee field. But the name and grossPay fields in the FullTimeEmployee class were given private visibility, which precludes their usage outside of the declaration of the FullTimeEmployee class. Can we change the visibility of those fields to public? That would be a bad choice, because then any user's code would be allowed to access (and modify) those fields. What we need is a visibility modifier for a superclass field that allows access by subclass methods but not by arbitrary user's code. The solution is found in the next section.

1.3.1 The protected Visibility Modifier

We noted above that subclass methods—but not user's code in general—should be able to access superclass fields. This suggests that we need a visibility modifier that is less restrictive than private (to allow subclass access) but more restrictive than public (to prohibit access by arbitrary user's code). The compromise between private and public visibility is protected visibility. We change the declaration of the FullTimeEmployee fields as follows:

protected string name;

protected double grossPay;

These declarations enable any subclass of FullTimeEmployee to access the name and grossPay fields as if they were declared within the subclass itself. This makes sense because an HourlyEmployee object is a FullTimeEmployee object as well. So the HourlyEmployee class has two inherited fields (name and grossPay) as well as those explicitly declared in HourlyEmployee (hoursWorked, payRate, and for convenience, regularPay and overtimePay).

The subclass HourlyEmployee can access all of the fields, from FullTimeEmployee, that have the protected modifier. Later on, if a subclass of HourlyEmployee is created, we would want that subclass's methods to be able to access the HourlyEmployee fields—as well as the FullTimeEmployee fields. So the declarations of the HourlyEmployee fields should also have the protected modifier:

protected int hoursWorked;

protected double payRate,
                 regularPay,
                 overtimePay;

The HourlyEmployee class will have a default constructor as well as the 3-parameter constructor defined earlier in Section 1.3. The FullTimeEmployee methods getName and getGrossPay are inherited as is by the HourlyEmployee class. The getHoursWorked, getPayRate, getRegularPay, and getOver timePay methods are explicitly defined in the HourlyEmployee class.

The toString() method from the FullTimeEmployee class will be overridden in HourlyEmployee to include the word "HOURLY". The override can be accomplished easily enough: we copy the code from the toString() method in FullTimeEmployee, and append "HOURLY" to the String returned:

return name + MONEY.format (grossPay) + "HOURLY";

But, as noted at the end of Section 1.2.3, copying code is dangerous. Instead, the definition of toString() in the HourlyEmployee class will call the toString() method in the FullTimeEmployee class. To call a superclass method, use the reserved word super as the calling object:

return super.toString()  +  "HOURLY";

Here is the complete HourlyEmployee.java file:

import java.text.DecimalFormat;


public class HourlyEmployee extends FullTimeEmployee implements Employee
{
   // for full-time hourly employees


   public final static int MAX_REGULAR_HOURS = 40;


   public final static double OVERTIME_FACTOR = 1.5;


   protected int hoursWorked;


   protected double payRate,
                    regularPay,
                    overtimePay;


   /**
    * Initializes this full-time HourlyEmployee object to have an empty string for
    * the name, 0 for hours worked, 0.00 for the pay rate, grossPay, regularPay
    * and overtimePay.
    *
    */
   public HourlyEmployee()
   {
       hoursWorked = 0;
       payRate = 0.00;
       regularPay = 0.00;
       overtimePay = 0.00;
   } // default constructor

/**
 * Initializes this full-time HourlyEmployee object's name and gross pay from a
 * a specified name, hours worked and pay rate.   If the hours worked is
 * at most MAX_REGULAR_HOURS, the gross pay is the hours worked times
 *  the pay rate. Otherwise, the gross pay is MAX_REGULAR_HOURS time the
 * pay rate, plus the pay rate times OVERTIME_FACTOR for all overtime hours.
 *
 * @param name - the specified name.
 * @param hoursWorked - the specified hours worked.
 * @param payRate - the specified pay rate.
 *
 */
public HourlyEmployee (String name, int hoursWorked, double payRate)
{
   this.name = name;
   this.hoursWorked = hoursWorked;
   this.payRate = payRate;


   if (hoursWorked <= MAX_REGULAR_HOURS)
   {
       regularPay = hoursWorked * payRate;
       overtimePay = 0.00;
   } // if
   else
   {
       regularPay = MAX_REGULAR_HOURS * payRate;
       overtimePay = (hoursWorked - MAX_REGULAR_HOURS) *
                     (payRate * OVERTIME_FACTOR);
   } // else
       grossPay = regularPay + overtimePay;
   } // 3-parameter constructor


  /**
   *  Returns the hours worked by this full-time HourlyEmployee object.
   *
   *  @return the hours worked by this full-time HourlyEmployee object.
   *
   *
  public int getHoursWorked()
  {
      return hoursWorked;
  } // method getHoursWorked



  /**
   * Returns the pay rate of this full-time HourlyEmployee object.
   *
   * @return the pay rate this full-time HourlyEmployee object.
   *
   */

     public double getPayRate()
     {
          return payRate;
     } // method getPayRate



     /**
      * Returns the regular pay of this full-time HourlyEmployee object.
      *
      * @return the regular pay this full-time HourlyEmployee object.
      *
      */
     public double getRegularPay()
     {
         return regularPay;
     } // method getRegularPay



     /**
      * Returns the overtime pay of this full-time HourlyEmployee object.
      *
      * @return the overtime pay this full-time HourlyEmployee object.
      *
      */
     public double getOvertimePay()
     {
         return overtimePay;
     } // method getOvertimePay



     /**
      * Returns a String representation of this full-time HourlyEmployee object with the
      * name followed by a space followed by a dollar sign followed by the gross pay
      * (with two fractional digits) followed by "FULL TIME HOURLY".
      *
      * @return a String representation of this full-time HourlyEmployee object.
      *
      */
     public String toString()
     {
         final String FULL_TIME_STATUS = "HOURLY";


         return super.toString() + FULL_TIME_STATUS;
     } // method toString


} // class HourlyEmployee

A final note on the visibility modifier protected: It can be applied to methods as well as to fields. For example, the visibility modifier for the getNextEmployee method in the Company class should be changed from private to protected for the sake of potential subclasses of Company. One such subclass is introduced in Section 1.3.3.

Section 1.3.2 continues our discussion of inheritance by examining the interplay between inheritance and constructors.

1.3.2 Inheritance and Constructors

Constructors provide initialization for instances of a given class. For that reason, constructors are never inherited. But whenever a subclass constructor is called, the execution of the subclass constructor starts with an automatic call to the superclass's default constructor. This ensures that at least the default initialization of fields from the superclass will occur. For example, the FullTimeEmployee class's default constructor is automatically invoked at the beginning of a call to any HourlyEmployee constructor. That explains how the name and grossPay fields are initialized in the HourlyEmployee class's default constructor.

What if the superclass has a constructor but no default constructor? Then the first statement in any subclass constructor must explicitly call the superclass constructor. A call to a superclass constructor consists of the reserved word super followed by the argument list, in parentheses. For example, suppose some class B's only constructor has an int parameter. If C is a subclass of B and C has a constructor with a string parameter, that constructor must start out by invoking B's constructor. For example, we might have

public C   (string s)
{
      super  (s.length());    // explicitly calls B's int-parameter constructor
      ...

}  // string-parameter constructor

So if a superclass explicitly defines a default (that is, zero-parameter) constructor, there are no restrictions on its subclasses. Similarly, if the superclass does not define any constructors, the compiler will automatically provide a default constructor, and there are no restrictions on the subclasses. But if a superclass defines at least one constructor and does not define a default constructor, the first statement in any subclass's constructor must explicitly invoke a superclass constructor.

1.3.3 The Subclass Substitution Rule

Just as the Company class used FullTimeEmployee, we can find the best-paid hourly employee with the help of an HourlyCompany class, which uses the HourlyEmployee class. HourlyCompany, a subclass of the Company class described in Section 1.2.3, differs only slightly from the Company class. Specifically, the main method invokes the HourlyCompany class's run method on a newly constructed HourlyCompany instance. Also, the getNextEmployee method is overridden to scan in the information for an HourlyEmployee object; to enable this overriding, we must change the visibility modifier of the Company class's getNextEmployee method from private to protected. Interestingly, the run and findBestPaid methods, which deal with full-time (not necessarily hourly) employees, are inherited, as is, from the Company class.

Assume the input file consists of

a 40 20
b 45 20
c 40 23

Then the output will be

The best-paid hourly employee (and gross pay) is b $950.00 FULL TIME HOURLY

Here is the HourlyCompany.java file:

import java.util.*;


import java.io.*;


public class HourlyCompany extends Company
{
    public static void main (String[ ] args) throws FileNotFoundException
    {
        new HourlyCompany().run();
    } // method main


   /**
    * Returns the next hourly employee from the specified Scanner object.
    *
    * @param sc - the Scanner object used to scan in the next employee.
    *
    * @return the next hourly employee scanned in from sc.
    *
    */
   protected HourlyEmployee getNextEmployee (Scanner sc)
   {
      Scanner lineScanner = new Scanner (sc.nextLine());


      String name = lineScanner.next();


      int hoursWorked = lineScanner.nextInt();


      double payRate = lineScanner.nextDouble();


      return new HourlyEmployee (name, hoursWorked, payRate);
   } // method getNextEmployee


} // class HourlyCompany

Recall, from the inherited findBestPaid method, the following assignment:

full = getNextEmployee  (sc);

The left-hand side of this assignment is (a reference to) a FullTimeEmployee object. But the value returned by the call to getNextEmployee is a reference to an HourlyEmployee object. Such an arrangement is legal because an HourlyEmployee is a FullTimeEmployee. This is an application of the Subclass Substitution Rule:

Subclass Substitution Rule

Whenever a reference-to-superclass-object is called for in an evaluated expression, a reference-to-subclass-object may be substituted.

Specifically, the left-hand side of the above assignment is a reference to a FullTimeEmployee object, so a reference to a FullTimeEmployee object is called for on the right-hand side of that assignment. So it is legal for that right-hand side expression to be a reference to an HourlyEmployee object; it is important to note that for an HourlyEmployee object, the toString() method includes "HOURLY". The returned reference is assigned to the FullTimeEmployee reference full, which is then used to update the FullTimeEmployee reference bestPaid. When the value of bestPaid is returned to the run method and the message bestPaid.toString() is sent, the output includes "HOURLY". Why? The reason is worth highlighting:

When a message is sent, the version of the method invoked depends on the run-time type of the object referenced, not on the compile-time type of the reference.

Starting with the construction of the new HourlyEmployee object in the getNextEmployee method, all of the subsequent references were to an HourlyEmployee object. So the version of the toString() method invoked by the message bestPaid.toString() was the one in the HourlyEmployee class. Let's take a closer look at the Subclass Substitution Rule. Consider the following:

FullTimeEmployee full = new FullTimeEmployee  ();

HourlyEmployee hourly = new HourlyEmployee();

full = hourly;

In this last assignment statement, a reference-to-FullTimeEmployee is called for in the evaluation of the expression on the right-hand side, so a reference-to-HourlyEmployee may be substituted: an HourlyEmployee is a FullTimeEmployee.

But the reverse assignment is illegal:

FullTimeEmployee full = new FullTimeEmployee  ();

HourlyEmployee hourly = new HourlyEmployee  ();

hourly = full;   // illegal

On the right-hand side of this last assignment statement, the compiler expects a reference-to-HourlyEmployee, so a reference-to-FullTimeEmployee is unacceptable: a FullTimeEmployee is not necessarily an HourlyEmployee. Note that the left-hand side of an assignment statement must consist of a variable, which is an expression. But that left-hand-side variable is not evaluated in the execution of the assignment statement, so the Subclass Substitution Rule does not apply to the left-hand side.

Now suppose we had the following:

FullTimeEmployee full = new FullTimeEmployee  ();

HourlyEmployee hourly = new HourlyEmployee  ();

full = hourly;

hourly = full;   // still illegal

After the assignment of hourly to full, full contains a reference to an HourlyEmployee object. But the assignment:

hourly = full;

still generates a compile-time error because the declared type of full is still reference-to-FullTimeEmployee. We can avoid a compile-time error in this situation with a cast: the temporary conversion of an expression's type to another type. The syntax for a cast is:

(the new type)expression

Specifically, we will cast the type of full to HourlyEmployee:

FullTimeEmployee full = new FullTimeEmployee  ();

HourlyEmployee hourly = new HourlyEmployee  ();

full = hourly;
hourly =  (HourlyEmployee)   full;

To put it anthropomorphically, we are saying to the compiler, "Look, I know that the type of full is reference-to-FullTimeEmployee. But I promise that at run-time, the object referenced will, in fact, be an HourlyEmployee object." The cast is enough to satisfy the compiler, because the right-hand side of the last assignment statement now has type reference-to-HourlyEmployee. And there is no problem at run-time either because—from the previous assignment of hourly to full—the value on the right-hand side really is a reference-to-HourlyEmployee.

But the following, acceptable to the compiler, throws a ClassCastException at run-time:

FullTimeEmployee full = new FullTimeEmployee  ();

HourlyEmployee hourly = new HourlyEmployee  ();

hourly =  (HourlyEmployee)   full;

The run-time problem is that full is actually pointing to a FullTimeEmployee object, not to an HourlyEmployee object.

The complete project, HourlyCompany, is in the ch1 directory of the book's website. Lab 1's experiment illustrates another subclass of FullTimEmployee.

You are now prepared to do Lab 1: The SalariedEmployee Class

Before we can give a final illustration of the Subclass Substitution Rule, we need to introduce the Object class. The Object class, declared in the file java.lang.Object.java, is the superclass of all classes. Object is a bare-bones class, whose methods are normally overridden by its subclasses. For example, here are the method specification and definition of the equals method in the Object class:

/**
 *  Determines if the calling object is the same as a specified object.
 *
 *  @param obj  - the specified object to be compared to the calling object.
 *
 *  @return true - if the two objects are the same.

 *
 */
public boolean equals   (Object obj)
{
   return  ( this == obj);
}  // method equals

The definition of this method compares references, not objects, for equality. So true will be returned if and only if the calling object reference contains the same address as the reference obj. For example, consider the following program fragment:

Object obj1 = new Object(),
       obj2 = new Object(),
       obj3 = objl;

System.out.println  (objl.equals  (obj2)   +  ""  + objl.equals  (obj3));

The output will be

false true

For that reason, this method is usually overridden by the Object class's subclasses. We saw an example of this with the String class's equals method in Section 0.2.3 of Chapter 0. In that equals method, the parameter's type is Object, and so, by the Subclass Substitution Rule, the argument's type can be string, a subclass of Object. For example, we can have

if (message.equals ("nevermore"))

1.3.4 Is-a versus Has-a

You will often encounter the following situation. You are developing a class B, and you realize that the methods of some other class, A, will be helpful. One possibility is for B to inherit all of A; that is, B will be a subclass of A. Then all of A's protected methods are available to B (all of A's public methods are available to B whether or not B inherits from A). An alternative is to define, in class B, a field whose class is A. Then the methods of A can be invoked by that field. It is important to grasp the distinction between these two ways to access the class A.

Inheritance describes an is-a relationship. An object in the subclass HourlyEmployee is also an object in the superclass FullTimeEmployee, so we can say that an HourlyEmployee is-a FullTimeEmployee.

On the other hand, the fields in a class constitute a has-a relationship to the class. For example, the name field in the FullTimeEmployee class is of type (reference to) string, so we can say a FullTimeEmployee has-a String.

Typically, if class B shares the overall functionality of class A, then inheritance of A by B is preferable. More often, there is only one aspect of B that will benefit from A 's methods, and then the better alternative will be to define an A object as a field in class B. That object can invoke the relevant methods from class A. The choice may not be clear-cut, so experience is your best guide. We will encounter this problem several times in subsequent chapters.

With an object-oriented approach, the emphasis is not so much on developing the program as a whole but on developing modular program-parts, namely, classes. These classes not only make the program easier to understand and to maintain, but they are reusable for other programs as well. A further advantage to this approach is that decisions about a class can easily be modified. We first decide what classes will be needed. And because each class interacts with other classes through its method specifications, we can change the class's fields and method definitions as desired as long as the method specifications remain intact.

The next section of this chapter considers the extent to which a language can allow developers of a class to force users of that class to obey the Principle of Data Abstraction.

1.4 Information Hiding

The Principle of Data Abstraction states that a user's code should not access the implementation details of the class used. By following that principle, the user's code is protected from changes to those implementation details, such as a change in fields.

Protection is further enhanced if a user's code is prohibited from accessing the implementation details of the class used. Information hiding means making the implementation details of a class inaccessible to code that uses that class. The burden of obeying the Principle of Data Abstraction falls on users, whereas information hiding is a language feature that allows class developers to prevent users from violating the Principle of Data Abstraction.

As you saw in Section 1.3.1, Java supports information hiding through the use of the protected visibility modifier for fields. Through visibility modifiers such as private and protected, Java forces users to access class members only to the extent permitted by the developers. The term encapsulation refers to the grouping of fields and methods into a single entity—the class—whose implementation details are hidden from users.

There are three essential features of object-oriented languages: the encapsulation of fields and methods into a single entity with information-hiding capabilities, the inheritance of a class's fields and methods by subclasses, and polymorphism, discussed in Section 1.5.

1.5 Polymorphism

One of the major aids to code re-use in object-oriented languages is polymorphism. Polymorphism—from the Greek words for "many" and "shapes"—is the ability of a reference to refer to different objects in a class hierarchy. For a simple example of this surprisingly useful concept, suppose that sc is a reference to an already constructed Scanner object. We can write the following:

FullTimeEmployee employee;  // employee is of type reference-to-FullTimeEmployee

if (sc.nextLine().equals ("full time"))
    employee = new FullTimeEmployee ("Doremus", 485.00);
else
    employee = new HourlyEmployee ("Kokoska", 45, 20);
system.out.println (employee.tostring());

Because the declared type of employee is reference-to-FullTimeEmployee, it is legal to write

employee = new FullTimeEmployee ("Doremus", 485.00);

So, by the Subclass Substitution Rule, it is also legal to write

employee = new HourlyEmployee ("Kokoska", 45, 20);

Now consider the meaning of the message

employee.tostring()

The version of the toString() method executed depends on the type of the object that employee is referencing. If the scanned line consists of "full time", then employee is assigned a reference to an instance of class FullTimeEmployee, so the FullTimeEmployee class's version of toString() is invoked. On the other hand, if the scanned line consists of any other string, then employee is assigned a reference to an instance of class HourlyEmployee, so the HourlyEmployee class's version of toString() is invoked.

In this example, employee is a polymorphic reference: the object referred to can be an instance of class FullTimeEmployee or an instance of class HourlyEmployee, and the meaning of the message employee.toString() reflects the point made in Section 1.3.3: When a message is sent, the version of the method invoked depends on the type of the object, not on the type of the reference. What is important here is that polymorphism allows code re-use for methods related by inheritance. We need not explicitly call the two versions of the toString() method.

The previous code raises a question: how can the Java compiler determine which version of toString() is being invoked? Another way to phrase the same question is this: How can the method identifier toString be bound to the correct definition—in FullTimeEmployee or in HourlyEmployee—at compile time, when the necessary information is not available until run time? The answer is simple: The binding cannot be done at compile-time, but must be delayed until run time. A method that is bound to its method identifier at run time is called a virtual method.

In Java, almost all methods are virtual. The only exceptions are for static methods (discussed in Section 2.1) and for final methods (the final modifier signifies that the method cannot be overridden in subclasses.) This delayed binding—also called dynamic binding or late binding —of method identifiers to methods is one of the reasons that Java programs execute more slowly than programs in most other languages.

Polymorphism is a key feature of the Java language, and makes the Java Collections Framework possible. We will have more to say about this in Chapter 4, when we take a tour of the Java Collections Framework.

Method specifications are method-level documentation tools. Section 1.6 deals with class-level documentation tools.

1.6 The Unified Modeling Language

For each project, we will illustrate the classes and relationships between classes with the Unified Modeling Language (UML). UML is an industry-standardized language, mostly graphical, that incorporates current software-engineering practices that deal with the modeling of systems. The key visual tool in UML is the class diagram. For each class—except for widely used classes such as string and Random—the class diagram consists of a rectangle that contains information about the class. The information includes the name of the class, its attributes and operations. For the sake of simplicity, we will regard the UML term attribute as a synonym for field. Similarly, the UML term operation will be treated as a synonym for method. For example, Figure 1.1 shows the class diagram for the FullTimeEmployee class from Section 1.2.2. For both attributes and operation parameters, the type follows the variable (instead of preceding the variable, as in Java).

In a class diagram, a method's parenthesized parameter-list is followed by the return type, provided the method actually does return a value. Visibility information is abbreviated:

  • +, for public visibility
  • -, for private visibility
  • #, for protected visibility

FIGURE 1.1 A UML class-diagram for the FullTimeEmployee class

image

Inheritance is illustrated by a solid arrow from the subclass to the superclass. For example, Figure 1.2 shows the relationship between the HourlyEmployee and FullTimeEmployee classes in Section 1.3.

A dashed arrow illustrates the relationship between a class and the interface that class implements. For example, Figure 1.3 augments the class diagrams from Figure 1.2 by adding the diagram for the Employee interface.

FIGURE 1.2 In UML, the notation for inheritance is a solid arrow from the subclass to the superclass

image

FIGURE 1.3 A UML illustration of the relationship between an interface, an implementing class, and a subclass

image

A non-inheritance relationship between classes is called an association, and is represented by a solid line between the class diagrams. For example, Figure 1.4 shows an association between the Company and FullTimeEmployee classes in the find-best-paid-employee project in Section 1.2.3.

In Figure 1.4, the symbol '*' at the bottom of the association line indicates a company can have an arbitrary number of employees. The number 1 at the top of the association line indicates that an employee works for just one company.

Sometimes, we want to explicitly note that the class association is a has-a relationship, that is, an instance of one class is a field in the other class. In UML, a has-a relationship is termed an aggregation, and is signified by a solid line between the classes, with a hollow diamond at the containing-class end.

FIGURE 1.4 The UML representation of an association between two classes

image

FIGURE 1.5 Aggregation in UML: the FullTimeEmployee class has a String field

image

For example, Figure 1.5 shows that the FullTimeEmployee class has a string field. To avoid clutter, the figure simply has the class name in each class diagram.

Graphical tools such as UML play an important role in outlining a project. We will be developing projects, starting in Chapter 5, as applications of data structures and algorithms. Each such project will include UML class diagrams.

SUMMARY

This chapter presents an overview of object-oriented programming. Our focus, on the use of classes rather than on their implementation details, is an example of data abstraction. Data abstraction —the separation of method specifications from field and method definitions—is a way for users of a class to protect their code from being affected by changes in the implementation details of the class used.

The three essential features of an object-oriented language are:

  1. Encapsulation of fields and methods into a single entity—the class—whose implementation details are hidden from users.
  2. Inheritance of a class's fields and methods by subclasses.
  3. Polymorphism: the ability of a reference to refer to different objects.

The Unified Modeling Language (UML) is an industry-standard, graphical language that illustrates the modeling of projects.

CROSSWORD PUZZLE

image

ACROSS DOWN
1. The ability of a subclass to give new definitions—applicable in the subclass—to methods defined in the superclass. 2. An example of an is-a relationship.
4. The ability of a reference to refer to different objects in a class hierarchy. 3. The principle that every class should be extendible through inheritance and still stable for existing applications.
5. The separation of what a class provides to users from how the fields and methods are defined. 6. The temporary conversion of an expression's type to another type.
7. The grouping of fields and methods into a single entity—the class—whose implementation details are hidden from users. 8. The superclass of all classes.
9. A collection of abstract methods and constants; the object-oriented term for "abstract data type."
10. In the Unified Modeling Language, a non-inheritance relationship between classes.

CONCEPT EXERCISES

1.1 Given that HourlyEmployee and SalariedEmployee are subclasses of FullTimeEmployee, suppose we have

FullTimeEmployee full = new FullTimeEmployee();
HourlyEmployee hourly = new HourlyEmployee ();
SalariedEmployee salaried = new SalariedEmployee  ();

full = salaried;

Which one of the following assignments would be legal both at compile-time and at run-time?

  1. salaried = (SalariedEmployee) full;
  2. salaried = full;
  3. salaried = (FullTimeEmployee) full;
  4. hourly = (HourlyEmployee) full;

Create a small project to validate your claim.

1.2 Assume that the classes below are all in the file Polymorphism.java. Determine the output when the project is run. Would the output be different if the call to println were System.out.println (a.toString())?

import java.util.*;


public class Polymorphism
{
         public static void main (String args [ ])
         {
             new Polymorphism().run();
         } // method main


         public void run()
         {
                 Scanner sc = new Scanner (System.in));


                 A a;


                 int code = sc.nextInt();


                 if (code == 0)
                           a = new A();
                 else       // non-zero int entered
                           a = new D();
                 System.out.println (a);
         } // method run


} // class Polymorphism

class A
{
         public String toString ()
         {
                 return "A";
         } // method toString


} // class A



class D extends A
{
         public String toString ()
         {
                    return "D";
         } // method toString


} // class D

1.3 In the Employee class, modify the tostring method so that the gross pay is printed with a comma to the left of the hundreds digit. For example, if the name is "O'Brien, Theresa" and the gross pay is 74400.00, the tostring method will return

O'Brien, Theresa $74,400.00

1.4 What can you infer about the identifier out from the following message?

system.out.println  ("Eureka!");

What is the complete declaration for the identifier out? Look in java.lang.System.java.

PROGRAMMING EXERCISES

1.1 Here is a simple class—but with method specifications instead of method definitions—to find the highest age from the ages scanned in:

public class Age
{
       protected int highestAge;


       /**
        * Initializes this Age object.
        *
        */
       public Age ()


       /**
        * Returns the highest age of the ages scanned in from the keyboard.

        * The sentinel is -1.
        *
        * @param sc - The Scanner used to scan in the ages.
        *
        * @return the highest age of the ages scanned in from sc.
        *
        */
       public int findHighestAge (Scanner sc)


} // class Age
  1. Fill in the method definitions for the Age class.
  2. Test your Age class by developing a project and running the project.

1.2 With the Age class in Programming Exercise 1.1.a. as a guide, develop a Salary class to scan in salaries from the input until the sentinel (—1.00) is reached, and to print out the average of those salaries. The average salary is the total of the salaries divided by the number of salaries.

1.3 This exercise presents an alternative to having protected fields. Modify the FullTimeEmployee class as follows: Change the visibility of the name and grossPay fields from protected to private, and develop public methods to get and set the values of those fields. A method that alters a field in an object is called a mutator, and a method that returns a copy of a field is called an accessor.

Here are the method specifications corresponding to the name field:

/**
 * Returns this FullTimeEmployee object's name.
 *
 *  @return a (reference to a) copy of this FullTimeEmployee object's
 *                name.
 *
 */
public String getName ()
/**
 * Sets this FullTimeEmployee object's name to a specifed string.
 *
 *  @param nameIn ' the String object whose value is assigned to this
 *                               FullTimeEmployee object's name.
 *
 */
public void setName (String nameIn)

1.4 Create a class to determine which hourly employee in a file received the most overtime pay. The name of the file is to be scanned in from System.in.

1.5 In the toString() method of the FullTimeEmployee class, there is a call to the format method. The heading of that method is

public final String format( double number)

What is the definition of that method?

Programming Project 1.1: A CalendarDate Class

In this project, you will develop and test a CalendarDate class. Here are the responsibilities of the class, that is, the services that the class will provide to users:

  1. to initialize a CalendarDate object to represent the date January 1, 2012;
  2. to initialize a CalendarDate object from integers for the month, day-of-month and year; if the date is invalid (for example, if the month, day-of-month and year are 6, 31 and 2006, respectively), use 1, 1, 2012;
  3. return, in string form, the next date after this CalendarDate object; for example, if this CalendarDate object represents January 31, 2012, the return value would be "February 1, 2012";
  4. return, in string form, the date prior to this CalendarDate object; for example, if this CalendarDate object represents January 1, 2013, the return value would be "December 31, 2012";
  5. return, in string form, the day of the week on which this CalendarDate object falls; for example, if this CalendarDate object represents the date December 20, 2012, the return value would be "Thursday";

Part a: Create method specifications for the above responsibilities.

Part b: Develop the CalendarDate class, that is, determine what fields to declare and then define the methods.

Part c: Create a project to test your CalendarDate class. Call each CalendarDate method at least twice.

1 Don't confuse method overriding with method overloading (discussed in Section 0.2.1 of Chapter 0): having two methods in the same class with the same method identifier but different signatures.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.255.178