Skip to main content.

Web Based Programming Tutorials

Homepage | Forum - Join the forum to discuss anything related to programming! | Programming Resources

JAVA Developer's Guide

Chapter 5 -- Classes and Objects

Chapter 5

Classes and Objects


CONTENTS




In this chapter you'll learn all about Java and object-oriented programming. You'll first cover general object-oriented programming concepts and then learn how to use Java classes to build object-oriented software. You'll use the information you learn to develop a sample Java program that illustrates the benefits of object-oriented programming.

Object-Oriented Programming Concepts

Over the many years since the dawn of computing, people have studied software-development approaches to figure out which approaches are quickest, cheapest, most reliable, and produce the best software. And over the years, many approaches and technologies have reigned as the best. As time goes on, we learn more about software development and are able to modify and adapt our approaches based on what we learn. The type of software we develop also changes over time as a result of improvements in computer hardware, innovations in computer science, and changes in user expectations. These improvements affect our development approaches as well.

Of all the known approaches to developing software, one approach, called the object-oriented approach, has repeatedly proven itself to be the best approach for a large class of common software applications. It's likely that the object-oriented approach will undergo further evolution and that a new, improved software-development paradigm will take its place. But for right now, and the foreseeable future, it is recognized as the best approach for the majority of software that we develop today.

Object-oriented programming focuses on the development of self-contained software components, called objects. These objects are modeled after things, such as files, forms, buttons, and windows, that appear in the real world. Objects are defined in terms of the information they contain and the operations they provide for using and manipulating this information.

It's an Object-Oriented World



This book is an object. It contains a lot of information. (If you don't believe me, try retyping it.) It also has methods for accessing the information it contains. For example, you can open the book, turn a page, read a paragraph, search the table of contents, and so on. The information contained in the book, together with the methods for accessing it, are what comprise the object known as this book.

In order to read this book, you need some sort of light source. You could be reading it in the open sunshine or by moonlight, but let's assume that you are using a lamp of some kind. A lamp is also an object. It is an example of an object that contains information about its state. The state of an object is the particular condition it is in. For example, a lamp can be on or off. The lamp's methods-turn lamp on and turn lamp off-are used to access the state of the lamp.

This book, too, has state information. For example, it can be open or closed. If it is open, it can be opened to a particular page. The pages are objects in their own right. They contain information and can be accessed through the read page method. The book object can be viewed as being composed of page objects. The book's methods provide access to pages, and the page methods provide access to the information contained on a particular page.

The information contained in an object, whether it is state specific or not, is referred to as the object's data. The object's methods are said to access the data. Some methods return information about the object's data and are said to support read access. Other methods cause the data to be modified and are said to provide write access to the data. Finally, as you'll learn in later sections, some methods, called constructors, are used to create objects.

Composition and Reuse

The fact that one object can be composed of, or built from, other objects is the heart of object-oriented programming. This allows more complex objects to be constructed from simple object components. Just as you would not write a book as one continuous stream of text (unless you are Jack Kerouac), you wouldn't write a program as a single sequence of source code instructions. You design your program as an application object and construct it from other objects that are built or borrowed.

For example, suppose you are developing a drawing program. Your drawing application would consist of objects such as windows, menus, a drawing canvas, a tool palette, a color palette, and so on. Some of these objects would be available in object libraries and others would be built from more primitive components. You would develop your drawing application by gathering and building its component objects and assembling them into an integrated whole.

Object composition not only allows you to simplify the organization of your programs, it also lets you reuse the software you develop. For example, you could develop drawing objects as part of your drawing program and then reuse those objects in a paint program and a desktop- publishing program. You could also package up your drawing objects and give or sell them to others so that they can use them as a foundation for building their own custom objects.

Object reuse provides you with the capability to build or acquire a library of objects from which you can more quickly and easily piece together your programs. Without this capability, you are forced to start from scratch with every program that you develop.

Object reuse is not limited to object composition. It also exploits a powerful capability of object-oriented programming known as inheritance. Inheritance not only allows objects to be used as is, but also allows new objects to be created by extending and tailoring existing objects. Before you learn about inheritance, however, the concept of an object's class must be explained.

Classes

At this point, you might be wondering just how you go about developing objects. The answer, of course, depends on the language you are using. Java, C++, Smalltalk, and some other object-oriented languages follow a class-based approach. This approach allows you to declare classes that serve as a template from which objects are created.

As you would expect, a class defines the type of data that is contained in an object and the methods that are used to access this data. A class also defines one or more methods to be used to create objects that are instances of the class. An instance of a class is a concrete manifestation of the class in your computer's memory.

For example, consider a job application form as an object. It contains data-the different form fields that must be filled out. There are also methods for accessing the data-for example, fill in form and read form. Now suppose that you develop an application form for a company that will use it for new job applicants. When a job is advertised, 100 potential applicants show up. In order for these applicants to use your form, they must all be given a unique instance of the form. These form instances are created by using the form you developed as a master copy and then duplicating the master copy as many times as needed to create each instance. The job applicants then fill in their instances of the form, using the fill in form method.

In the preceding example, the master form is analogous to a class. The master form defines the data to be contained in each of its instances and implicitly provides methods by which the data can be accessed. In the same way, a class defines the data that can be contained in an object as well as the methods that can be used to access this data.

Classification and Inheritance

Classification is a common way that we organize knowledge. When we encounter a new object in our daily experience, we try to fit that object in our hierarchical classification scheme. If it fits in an existing category, we know what kind of object it is. If it doesn't fit, we add a new category. Figure 5.1 describes how we use classification to represent knowledge.

Figure 5.1. Hierarchical classification of knowledge.

Figure 5.1 : Hierarchical classification of knowledge.

When we classify objects in this hierarchical fashion, the object categories at the top of the classification tree include all the object categories below them. If an object category appears in the classification tree, it satisfies the properties of all object categories above it in the tree. Figure 5.2 presents a classification tree for vehicles. All categories in the tree below the category automobile, for example, share the common characteristics of being four-wheeled, self-powered, and designed for passenger transportation.

Figure 5.2 : Vehicle classification tree.

The fact that a lower-level category shares the characteristics of the categories above it on the classification tree is known as inheritance. The lower-level categories are said to inherit the characteristics of the categories above them on the tree.

At this point, you're probably wondering what any of this has to do with object-oriented programming in general, and Java software development in particular. We're almost there.

The classes you learned about in the previous section can also be organized in a hierarchical fashion. A class X is said to extend another class Y if it contains all the data contained in class Y and implements all the methods implemented by class Y. Class X is said to be a subclass of class Y, and class Y is said to be a superclass, or parent class, of class X.

Classes form a hierarchical classification tree under the subclass relationship. If a class X is a subclass of a class Y, it inherits the properties of Y. This means that all of the data and methods defined for class Y are available to class X.

Most object-oriented programming languages, and Java in particular, allow you to easily define subclasses that automatically inherit the data and methods of the classes they extend. This is a very powerful feature for software reuse. Not only can you reuse classes as they are defined, but you can easily extend and tailor their definitions by adding additional data and access methods to their subclasses.

There are many times that you may have a class definition you can use in your program, but it would be better if it supported additional state information or access methods. Java's support of subclassing enables you to easily extend such classes by supplying only the additional data and methods that are unique to the subclass. This allows you to take advantage of all the features of the superclass without having to implement any of them.

Multiple Inheritance

When a class extends another class, it inherits the data and methods of the class it extends. This is known as single inheritance. It is also possible for a class to extend classes on more than one branch of the class hierarchy tree, as shown in Figure 5.3. This is known as multiple inheritance.

Figure 5.3 : Multiple inheritance.

Multiple inheritance poses some difficulties for object-oriented programming. Most of these difficulties stem from the problem of determining which parent of a class to use under certain conditions. Numerous ambiguities arise when a class may have more than one immediate parent. For example, suppose a class X extends both a class Y and a class Z. Both class Y and class Z implement a unique print method. How does the compiler determine what method should be used to print objects of class X? What if the ambiguity arises during runtime for an object that inherits methods over several widely spaced branches of the class hierarchy? What's a poor compiler to do?

It is possible to design compilers and runtime systems that solve the ambiguities resulting from multiple inheritance, but these solutions tend to introduce a significant amount of processing overhead, adversely affecting program size and performance. The developers of Java have opted to support only single inheritance. This greatly simplifies the Java language, compiler, and runtime system. Java uses the interface construct to provide the benefits of multiple inheritance without the drawbacks resulting from parent ambiguity. You'll learn more about this construct in Chapter 6, "Interfaces."

Messages, Methods, and Object Interaction

In a pure object-oriented programming model, such as that used by Smalltalk, objects interact by sending messages to each other. When an object receives a message, the object invokes a method to process the message. The method may change the state of the object, return information contained in the object, or cause objects to be created or deleted.

The object model used by Java is consistent with the concept of message passing, but does not emphasize it. In the Java model, objects interact by invoking each other's methods. Methods provide access to the information contained in an object. The type of access varies depending on the method.

Encapsulation

One of the characteristics of object-oriented programming that is often touted in discussions of the subject is encapsulation. The term carries the connotation of an object being enclosed in some sort of container-and that is exactly what it means. Encapsulation is the combining of data and the code that manipulates that data into a single component-that is, an object. Encapsulation also refers to the control of access to the details of an object's implementation. Object access is limited to a well-defined, controlled interface. This allows objects to be self-contained and protects them from accidental misuse, both of which are important to reliable software design.

Polymorphism

Polymorphism is the ability to assume different forms. In object-oriented programming, this refers to the ability of objects to have many methods of the same name, but with different forms. The compiler and runtime system support polymorphism by matching each method invocation to the correct method, class, and object. The ability to figure out which method to use, in complex situations, is the essence of polymorphism.

Luckily for us, polymorphism is implemented in the compiler and runtime system-we don't need to do anything to make it happen. We just need to know that it works.

Dynamic Binding

Sometimes a program might need to interface with objects of many different classes. For example, consider a program that has the responsibility of sending out objects over a communication link. The program may not know what class an object belongs to until it is time to send it. The capability to defer until runtime decisions about what class an object belongs to and the methods for accessing the object is known as dynamic binding.

Dynamic binding is important to object-oriented programming because it eliminates many potentially constraining assumptions about the classes that an object belongs to and enables objects to be designed in a more general and open manner.

Dynamic binding also provides capabilities that are necessary for the advanced network programming capabilities of Java applets. When a browser executes a Java applet, the applet could require the loading of classes located on other sites across the Internet. Furthermore, these classes could be in a continual state of modification and upgrade. Dynamic binding allows new and modified objects to be used by executing software without requiring recompilation. The compiler and interpreter work together to provide executable code with the capabilities needed to dynamically interface with unknown objects during program execution.

Java Classes

In this section, you will develop a Java program, CDrawApp, that illustrates the concepts of encapsulation, composition and reuse, classification and inheritance, polymorphism, and dynamic binding. CDrawApp will allow you to draw points, boxes, and text strings on a character-based grid that is displayed using the Java console window.

The program will be introduced as a series of Java classes that cover different aspects of class and object syntax. Each new class will make use of the capabilities provided by previous classes and will provide additional building blocks required for the program's development. It is important that you understand each class that is presented before moving on to subsequent classes.

You should create a ch05 directory, under c:\java\jdg, to store the Java source and bytecode files for this lesson. All classes will be developed in the jdg.ch05 package.

Class Syntax

The Java language is class and object oriented. Classes are templates for the creation of objects. They define the data contained in an object together with methods for accessing that data.

Classes are declared as follows:

ClassModifiers class ClassName ExtendsClause ImplementsClause ClassBody

The ClassModifiers, ExtendsClause, and ImplementsClause are optional. Interfaces and the ImplementsClause are covered in the next chapter. The ClassBody is enclosed by braces, and contains zero or more field declarations.

An example of a simple class declaration is

class SimpleClass {
}

It declares a class, named SimpleClass, that is a subclass of Object, the highest-level class in the Java class hierarchy. SimpleClass declares no variables or methods of its own; it has only those that it inherits from Object.

The Point Class

The first class that you'll define for the CDrawApp program is the Point class. This class is used to identify a point on a grid by its x- and y-coordinates. The source code for the class declaration follows. (See Listing 5.1.) You should enter the code in a file named Point.java and store it in your c:\java\jdg\ch05 directory. Then compile it using the command javac Point.java.


Listing 5.1. The Point class source code.

package jdg.ch05;

// Point.java

public class Point {
 // Variable declarations
 private int x;
 private int y;

 //Method declarations
 public Point() {
  x = 0;
  y = 0;
 }
 public Point(int xValue, int yValue) {
  x = xValue;
  y = yValue;
 }
 public Point(Point p) {
  x = p.x();
  y = p.y();
 }
 public int x() {
  return x;
 }
 public int y() {
  return y;
 }
 public void xSet(int xValue) {
  x = xValue;
 }
 public void ySet(int yValue) {
  y = yValue;
 }
 public Point add(Point p) {
  return new Point(x+p.x(), y+p.y());
 }
 public Point add(int i,int j) {
  return new Point(x+i,y+j);
 }
 public String toString() {
  return new String("("+String.valueOf(x)+","+String.valueOf(y)+")");
 }
}

Class Modifiers

The Point class is declared using the public class modifier. Class modifiers are keywords that are used to specify the properties of a class. Three class modifiers are supported: public, final, and abstract. If a class is declared as public, it can be accessed outside of its package; otherwise, it cannot. Because Point is declared as public, it can be accessed outside its package. Only one public class or interface is allowed in a compilation unit. Point is the only class in Point.java and, therefore, follows this rule.

If a class is declared as final, it cannot be extended. Final classes form the leaves of the class hierarchy tree.

An abstract class is used to define the general behavior for an intended set of subclasses. abstract classes are used to set the stage for subsequent subclass development. They are, by definition, incomplete and cannot be instantiated in terms of objects. abstract classes describe the behavior expected of their subclasses through the declaration of abstract methods. abstract methods must be redefined, or overridden, before they can be used. Only abstract classes are allowed to declare or inherit abstract methods. The CGObject class is an example of an abstract class. It is presented in the section "The CGObject class," later in this chapter.

Extending Superclasses

When a class declaration does not contain an extends clause, the class is automatically made a subclass of the Object class. The Point class does not contain an extends clause and, therefore, is a subclass of Object. The Object class is at the top of the Java class hierarchy, being the superclass of all Java classes.

You can change a class's position in the class hierarchy by identifying its immediate super-class in the class declaration. The immediate superclass is the parent class directly above it in the class hierarchy. You identify the immediate superclass of a class using the extends clause in the class declaration. For example, you can place SimpleClass under ExampleClass in the class hierarchy as follows:

class SimpleClass extends ExampleClass {
}

SimpleClass does not have any unique variables or methods of its own, but it inherits those of ExampleClass and all superclasses of ExampleClass.

Adding Body to Classes

The body of the Point class consists of all declarations between the opening and closing braces. If a class is to add any features to its superclass, it does so in its class body. It is here that additional variables and methods are declared. These additional declarations are referred to as field declarations.

The field declarations are identified within the opening and closing braces ({ and }) of the class body. You need to supply the braces even if you don't intend to declare any fields, as you saw in the SimpleClass example.

Variable Declarations

Variables are the components of an object that store data and state information. They are declared as follows:

VariableModifiers Type VariableDeclarators

VariableModifiers are keywords that identify special properties of the variables being declared. Type is the Java type of the declared variables. It may be a primitive type, a class type, an interface type, or an array type. VariableDeclarators identify the names of the declared variables and can be used to specify the initial values of these variables.

The Point class declares two integer variables: x and y. They are used to store the location of a point on a two-dimensional grid. These variables are declared with the private modifier. This modifier restricts access to the x and y variables to within the Point class. The Point class illustrates the principles of encapsulation. Data and methods are combined with a well-defined interface to provide maximum modularity. Access to the internal operation of the class is controlled.

Constructor Declarations

Constructors are special methods that are used to initialize newly created objects. They are used together with the new operator to create and initialize objects that are instances of a class.

Constructors are declared in a slightly different manner than other methods. Their syntax is as follows:

AccessSpecifier ConstructorDeclarator ThrowsClause ConstructorBody

AccessSpecifier identifies the type of access allowed to the constructor. ConstructorDeclarator identifies a method with the same name as the class and specifies its parameter list. The parameter list is a comma-separated list of parameter declarations where each parameter declaration identifies the type and name of a parameter that is passed to the constructor upon invocation.

ThrowsClause is a constructor. Exceptions are covered in Chapter 7, "Exceptions."

ConstructorBody contains the code that implements the constructor.

The Point class has three constructors:

 public Point() {
  x = 0;
  y = 0;
 }
 public Point(int xValue, int yValue) {
  x = xValue;
  y = yValue;
 }
 public Point(Point p) {
  x = p.x();
  y = p.y();
 }

All three constructors are identified as public. This allows them to be accessed outside of their package. The first constructor does not have any parameters. It simply initializes the x and y variables to 0. The second constructor has two parameters, xValue and yValue, of integer type. They are used to set the value of x and y. The third constructor takes an object of class Point as its parameter. It sets the values of x and y based upon the values of the parameter point p. It is an example of a copy constructor because it creates a new point that is a copy of the point that is passed as its parameter.

The declaration of the three Point constructors is an example of overloading. Overloading occurs when two or more methods with the same name are declared within a class. The overloaded methods must differ in their parameter lists. The add() methods of the Point class are also overloaded.

Overloading is an example of polymorphism. When an overloaded method is to be invoked during program execution, the number and type of method arguments used in the invocation determine which method is used.

Access Method Declarations

Methods are executable units of code that provide access to the data stored in variables. Methods that are not constructors are referred to as nonconstructor methods, or access methods. Access methods are declared within the body of a class as follows:

MethodModifiers ResultType MethodDeclarator ThrowsClause MethodBody

MethodModifiers identify special properties of a method. All the methods of class Point are public, allowing them to be accessed outside of their package.

ResultType of a method identifies the type of value that is returned by the method. If an access method does not return a value, it must use the void return type. Constructors do not have a return type. The access methods of class Point have return values of type int, void, Point, and String.

MethodDeclarator identifies the method, by name, and specifies its parameter list. The parameter list of access methods is specified in the same manner as with constructors.

MethodBody contains the code that implements the Java method.

The Point class has seven access methods. The x() and y() methods return the x- and y-coordinates of a point. The xSet() and ySet() methods set the values of these coordinates based on the values of the xValue and yValue parameters. The two add() methods are used to create a new Point object by adding to the coordinates of the point being accessed. The new operator creates new instances of a class. It is always followed by a constructor that initializes the newly created instance. The toString() method returns an object of class String that describes the point as an ordered pair.

The CGrid Class

The CGrid class is used to define a grid of characters of specified dimensions. It provides a basic set of grid methods and is extended by other classes that add to these methods. Its source code is shown in Listing 5.2. It should be entered into the CGrid.java file and compiled using javac.


Listing 5.2. The CGrid class source code.

package jdg.ch05;

// CGrid.java

public class CGrid {
 // Variable declarations
 protected int width;
 protected int depth;
 protected char grid[][];

 // Method declarations
 public CGrid(int widthValue,int depthValue) {
  width = widthValue;
  depth = depthValue;
  grid = new char[depth][width];
  blankGrid();
 }
 public void blankGrid() {
  fillGrid(' ');
 }
 public void fillGrid(char ch) {
  for(int j=0; j<depth; ++j)
   for(int i=0; i<width; ++i)
    grid[j][i]= ch;
 }
 public void putCharAt(char ch,Point p){
  grid[p.y()][p.x()] = ch;
 }
 public char getCharFrom(Point p) {
  return grid[p.y()][p.x()];
 }
}

CGrid declares three variables: width, depth, and grid[][]. The width and depth variables are used to specify the horizontal and vertical dimensions of grid[][], an array of character arrays that holds the characters of the grid. The grid[][] array is used as a two-dimensional array of characters, even though Java technically does not have multidimensional arrays.

The CGrid variables are declared as protected. This specifies that they can only be accessed in the package, jdg.ch05, in which they are declared, and in any subclasses of CGrid.

CGrid has a single constructor that sets the values of width and depth, allocates the grid[][] array, and then invokes blankGrid() to fill grid[][] with spaces.

CGrid has four access methods. The fillGrid() method sets each element of the grid[][] array to the ch parameter. The blankGrid() method simply calls fillGrid() with a space character. The putCharAt() and getCharFrom() methods are used to set a point in the grid to a particular character and to find out what character is at a given location in the grid.

Note that the putCharAt() and getCharFrom() methods use the Point class to define their parameters. Because Point is in the same package as CGrid, it does not need to be imported.

The CGObject Class

The CGObject class is an example of an abstract class. abstract classes are used to constrain the behavior of their subclasses by defining abstract methods. The abstract methods must be implemented by any non-abstract subclasses. Listing 5.3 shows the source code of the CGObject class.


Listing 5.3. The CGObject class source code.

package jdg.ch05;

// CGObject.java

public abstract class CGObject {
 // Variable declarations
 public Point location;
 public char drawCharacter;

 // Method declarations
 public void addToGrid(PrintCGrid grid) {
  grid.addCGObject(this);
 }
 public abstract void display(PrintCGrid grid);
 public abstract void describe();
}


The CGObject class is used to define the general behavior of objects that may be displayed on a grid. It declares two variables: location and drawCharacter. The location variable is of type Point and is used to specify the point on a grid where an object is located. The drawCharacter variable identifies the character that should be used to draw the object.

CGObject has three methods and no constructors. abstract classes cannot have constructors because they are incompletely defined and, therefore, cannot have object instances.

The first method, addToGrid(), is not abstract. It takes an object of class PrintCGrid as a parameter and invokes the addCGObject() method of PrintCGrid to add this to the grid. The this keyword is used to refer to the current object. Whatever object of a subclass of CGObject that is invoked with the addToGrid() method is added to an object of class PrintCGrid.

CGObject's other two methods are declared with the abstract keyword. This signifies that they must be overridden before they can be used by any non-abstract subclasses of CGObject. The overridden methods must have the same names, parameters, and return values as the abstract methods. The display() method will be used to display an object on a grid of class PrintCGrid. The describe() method will be used to display a description of a grid object.

Don't forget to enter and compile CGObject before going on to the next class. In case you forgot, it should be entered into a file of the same name, with the .java extension-that is, CGObject.java.

The PrintCGrid Class

The PrintCGrid class is a subclass of the CGrid class. It defines additional variables and methods that allow objects to be added to a grid. It also provides methods for displaying the grid. The source code of the PrintCGrid class is shown in Listing 5.4.


Listing 5.4. The PrintCGrid class source code.

package jdg.ch05;

import java.lang.System;

// PrintCGrid.java

public class PrintCGrid extends CGrid {
 // Variable declarations
 protected CGObject displayList[];
 protected static final int maxObjects = 100;
 protected int numObjects;

 // Method declarations
 public PrintCGrid(int x,int y) {
  super(x,y);
  numObjects = 0;
  displayList = new CGObject[maxObjects];
 }
 public void addCGObject(CGObject obj) {
  if(numObjects < maxObjects) {
   displayList[numObjects] = obj;
   ++numObjects;
  }
 }
 public void deleteCGObject(int index) {
  if(index < numObjects && numObjects > 0) {
   for(int i = index; i < numObjects -1 ; ++i)
    displayList[i] = displayList[i+1];
   --numObjects;
  }
 }
 public void deleteLastObject() {
   if(numObjects > 0) --numObjects;
 }
 public int getNumObjects() {
  return numObjects;
 }
 public CGObject getObject(int index) {
  return displayList[index];
 }
 public void clearGrid() {
  numObjects = 0;
 }
 public void drawGrid() {
  blankGrid();
  for(int i = 0; i < numObjects ; ++i)
   displayList[i].display(this);
 }
 public void displayGrid() {
  for(int i=0;i<depth;++i)
   System.out.println(String.valueOf(grid[i]));
 }
 public void displayRow(int row) {
  System.out.print(String.valueOf(grid[row]));
 }
 public void show() {
  drawGrid();
  displayGrid();
 }
}


PrintCGrid is identified as a subclass of CGrid by the extends clause in the PrintCGrid class declaration. This means that all the variables and methods defined in CGrid are available to PrintCGrid. You should now begin to get a feel for the power of inheritance. PrintCGrid uses CGrid as a base to which other grid display variables and methods are added.

PrintCGrid declares three variables: displayList[], maxObjects, and numObjects. These variables are declared as protected, thereby limiting their access to the jdg.ch05 package and subclasses of PrintCGrid.

The displayList[] variable is an array of class CGObject. This does not mean that it will contain objects that are instances of this class. That would be impossible, because CGObject is abstract. Declaring displayList[] to be an array of class CGObject allows it to hold objects of any class that is a subclass of CGObject. In general, if a variable is declared to be of class X, then the variable can be assigned any object of a class that is a subclass of X.

The maxObjects variable is declared as both static and final. Variables that are declared using the static modifier are common to all objects that are instances of a class and are not replicated for each instance. Static variables are referred to as class variables. Variables that aren't declared as static are instance variables and are replicated for each object that is an instance of a class.

The final modifier is used to identify a variable as a constant. A variable that is declared with the final modifier must be initialized in its declaration and cannot be assigned a value anywhere else outside its declaration. The maxObjects constant is initialized to 100. It is used to identify the maximum number of objects that can be added to displayList[].

The numObjects variable is used to count the actual number of objects that have been added to the grid's displayList[].

PrintCGrid has a single constructor. This constructor has two parameters, x and y, that represent the horizontal and vertical dimensions of the grid. The constructor invokes the super() method, passing these variables as arguments. The super() method is an example of a constructor call statement. It invokes the constructor of PrintCGrid's superclass, that is, CGrid, with the arguments x and y. CGrid's constructor initializes its width and depth variables, allocates the grid[][] array, and fills it with spaces. When CGrid's constructor is finished, PrintCGrid's constructor continues by setting numObjects to 0 and allocating the displayList[] array.

PrintCGrid provides 10 access methods. The addCGObject() method adds an object to the displayList[] array. The deleteCGObject() method deletes the object at the specified index. All subsequent objects in the array are moved to fill the hole left by the deleted object. The deleteLastObject() method deletes the last object by merely decrementing numObjects.

The getNumObjects() method returns the number of objects in displayList[]. The getObject() method returns the object at the specified position within displayList[]. The clearGrid() method clears all objects by setting numObjects to 0.

The drawGrid() method is an interesting example of dynamic binding and the use of abstract classes. It blanks the grid, using the method that it inherits from CGrid, and then invokes the display() method of each object in displayList[]. It does not know what kind of objects are contained in displayList[]. It only knows that they are of some subclass of CGObject, and therefore must implement the display() method. Dynamic binding enables the display() method to be invoked for the correct object class.

The displayGrid() method displays each row of the grid to the console window. It is an example of inheritance. The grid[][] array was defined in the CGrid class and inherited by PrintCGrid. It is updated by drawGrid() and the display() methods of all subclasses of CGObject. It is used by PrintCGrid to print characters to the console window.

The valueOf() method used in displayGrid() is a static method of the String class. It converts an array of characters into a String object. A static method is similar to a static variable in that it applies to the class as a whole rather than to objects that are instances of the class. Because of this class orientation, a static method can access only static variables. All static methods are final. final methods cannot be overridden.

The displayRow() method displays a single row of the grid to the console window and the show() method combines the drawGrid() and displayGrid() methods into a single method.

The BorderedPrintCGrid Class

The BorderedPrintCGrid class further extends the CGrid class by subclassing PrintCGrid. It adds additional variables and methods for creating a border around objects of class PrintCGrid. Listing 5.5 contains the source code of the BorderedPrintCGrid class.


Listing 5.5. The BorderedPrintCGrid class source code.

package jdg.ch05;

// BorderedPrintCGrid.java

public class BorderedPrintCGrid extends PrintCGrid {
 // Variable declarations
 private boolean useBorder;
 private char borderCharacter;
 private String horizEdge;
 private String vertEdge;

 // Method declarations
 public BorderedPrintCGrid() {
  super(75,20);
  setBorderDefaults('*');
 }
 public BorderedPrintCGrid(int x,int y,char ch) {
  super(x,y);
  setBorderDefaults(ch);
 }
 private void setBorderDefaults(char ch) {
  useBorder = true;
  setBorderCharacter(ch);
 }
 public void enableBorder(boolean toggle) {
  useBorder = toggle;
 }
 public void setBorderCharacter(char ch) {
  borderCharacter = ch;
  char border[] = new char[width+2];
  for(int i=0;i<width+2;++i) border[i] = borderCharacter;
  horizEdge = new String(border);
  vertEdge = String.valueOf(borderCharacter);
 }
 public void displayGrid() {
  if(useBorder) {
   System.out.println(horizEdge);
   for(int i=0;i<depth;++i) {
    System.out.print(vertEdge);
    displayRow(i);
    System.out.println(vertEdge);
   }
   System.out.println(horizEdge);
  }else super.displayGrid();
 }
}


BorderedPrintCGrid declares four private variables: useBorder, borderCharacter, horizEdge, and vertEdge. The useBorder variable is of type boolean and determines whether a border should be displayed. The borderCharacter variable contains the character to be used to display the border. The horizEdge and vertEdge variables contain the String objects to be displayed for the horizontal and vertical edges of the border. These objects are computed from the borderEdge character based on the grid[][] dimensions.

BorderedPrintCGrid has two constructors. The first does not take any parameters. It constructs a grid 75 characters wide and 20 rows high by calling the constructor of PrintCGrid. Note that PrintCGrid's constructor passes the call farther up the class hierarchy to CGrid's constructor. This is an example of how classification and inheritance work together to simplify the development of new classes and methods. The setBorderDefaults() method is used to initialize the variables of BorderedPrintCGrid. The second constructor is similar to the first, but provides the capability for the grid's dimensions to be specified directly.

BorderedPrintCGrid provides four access methods. The setBorderDefaults() method initializes the variables of the BorderedPrintCGrid class using the enableBorder() and setBorderCharacter() methods. The enableBorder() method allows the useBorder variable to be set to true or false. The setBorderCharacter() method sets the borderCharacter, horizEdge, and vertEdge variables for use by the displayGrid() method.

The displayGrid() method overrides the displayGrid() method of the PrintCGrid class. By doing so, it redefines the method to suit its own needs. It checks to see if the useBorder variable is true. If it is true, then a bordered grid is displayed using the displayRow() method of PrintCGrid. If it is false, it invokes the displayGrid() method of its superclass, PrintCGrid, to display the grid. The super keyword is used to identify the fact that a superclass method should be used instead of the one defined for the current class. The name of the superclass can also be used to indicate which method should be used. The method invocation could have used PrintCGrid.displayGrid() instead of super.displayGrid().

The CGPoint Class

The CGPoint class shows how a non-abstract class extends an abstract class. The CGPoint class extends CGObject. It does not add any new variables to those that it inherits, and the only methods that it declares are constructors and the abstract methods that it is required to implement. Listing 5.6 shows the source code of the CGPoint class.


Listing 5.6. The CGPoint class source code.

package jdg.ch05;

// CGPoint.java

public class CGPoint extends CGObject {
 // Method declarations
 public CGPoint(int x, int y,char ch) {
  location = new Point(x,y);
  drawCharacter = ch;
 }
 public CGPoint(int x, int y) {
  this(x,y,'+');
 }
 public CGPoint(Point p) {
  this(p.x(),p.y(),'+');
 }
 public CGPoint(Point p,char ch) {
  this(p.x(),p.y(),ch);
 }
 public void display(PrintCGrid grid) {
  grid.putCharAt(drawCharacter,location);
 }
 public void describe() {
  System.out.print("CGPoint "+String.valueOf(drawCharacter)+" ");
  System.out.println(location.toString());
 }
}


CGPoint has four constructors. The first takes the x- and y-coordinates of a point and the character to be displayed, and initializes the location and drawCharacter variables declared in CGObject. The remaining constructors invoke the this() constructor. The this() constructor invokes a constructor for the current class that has a matching argument list. The matching constructor is the first constructor, in all three cases. The second, third, and fourth constructors provide a variety of parameter lists by which objects of CGPoint can be constructed. The second and third constructors supply default values for drawCharacter.

The this() constructor call statement is similar to the super() constructor call statement used with the PrintCGrid and BorderedPrintCGrid classes. Each allows part of the construction details to be handed off to other constructors in the same and parent classes. If no constructor call statement is used, a default super() constructor is used. This ensures that when an object is created, constructors from all of its superclasses are called to initialize all variables inherited by the object.

CGPoint overrides the display() and describe() abstract methods defined by CGObject. The display() method uses the putCharAt() method defined for class CGrid to draw a character in the grid[][] array. The describe() method prints a description of the point to the console window. It uses the toString() method of the Point class.

The CGBox Class

The CGBox class also extends CGObject. It provides an additional variable to allow a rectangle to be displayed on a grid. Listing 5.7 contains the CGBox class source code.


Listing 5.7. The CGBox class source code.

package jdg.ch05;

// CGBox.java

public class CGBox extends CGObject {
 // Variable declarations
 protected Point lr; // Lower right corner of a box

 // Method declarations
 public CGBox(Point ulCorner, Point lrCorner,char ch) {
  location = ulCorner;
  lr = lrCorner;
  drawCharacter = ch;
 }
 public CGBox(Point ulCorner, Point lrCorner) {
  this(ulCorner,lrCorner,'#');
 }
 public void display(PrintCGrid grid) {
  int width = lr.x() - location.x() + 1;
  int height = lr.y() - location.y() + 1;
  Point topRow = new Point(location);
  Point bottomRow = new Point(location.x(),lr.y());
  for(int i=0; i<width; ++i) {
   grid.putCharAt(drawCharacter,topRow);
   grid.putCharAt(drawCharacter,bottomRow);
   topRow = topRow.add(1,0);
   bottomRow = bottomRow.add(1,0);
  }
  Point leftCol = new Point(location);
  Point rightCol = new Point(lr.x(),location.y());
  for(int i=0;i<height;++i){
   grid.putCharAt(drawCharacter,leftCol);
   grid.putCharAt(drawCharacter,rightCol);
   leftCol = leftCol.add(0,1);
   rightCol = rightCol.add(0,1);
  }
 }
 public void describe() {
  System.out.print("CGBox "+String.valueOf(drawCharacter)+" ");
  System.out.println(location.toString()+" "+lr.toString());
 }
}


The location variable defined in CGObject is used as the upper-left corner of the rectangle. The lr variable defined by CGBox is the lower-right corner of the rectangle.

CGBox provides two constructors. The first takes arguments for the upper-left and lower-right corners of the rectangle and a character to be used as the drawCharacter. The second constructor provides for a default box drawCharacter and uses a call to the first constructor to accomplish its initialization.

The display() method displays a box on an object of class PrintCGrid. It is a good example of object composition because it uses objects of several different classes to accomplish this purpose. It begins by calculating the box's width and height dimensions from the location and lr variables. The location variable is the upper-left corner of the box, and the lr variable is the lower-right corner. It then creates two points, topRow and bottomRow, that will be used to step through the top and bottom rows of the box's display. The first for statement is used to display the box's top and bottom rows. The putCharAt() method of CGrid is used to display the drawCharacter at the locations specified by topRow and bottomRow. The putCharAt() method is inherited by objects of class PrintCGrid. The x-coordinates of the topRow and bottomRow variables are incremented by 1 to step through the rows' display using the add() method of the Point class.

The display() method creates the leftCol and rightCol points to be used to step through the display of the box's left and right columns. The second for statement displays these columns in a similar manner to the first for statement. The y-coordinates of the leftCol and rightCol variables are incremented by 1 to step through the columns' display.

The describe() method displays a description of a box's parameters to the console window. This description identifies the drawCharacter and upper-left and lower-right corners of the box.

The CGText Class

The CGText class is the third and final subclass of CGObject that is declared in this chapter. (See Listing 5.8.) The CGObject class hierarchy is extended further in Chapter 6.


Listing 5.8. The CGText class source code.

package jdg.ch05;

// CGText.java

public class CGText extends CGObject {
 // Variable declarations
 String text;

 // Method declarations
 public CGText(Point p,String s) {
  location = p;
  drawCharacter = ' ';
  text = s;
 }
 public void display(PrintCGrid grid) {
  Point p = new Point(location);
  for(int i=0;i<text.length();++i){
   grid.putCharAt(text.charAt(i),p);
   p = p.add(1,0);
  }
 }
 public void describe() {
  System.out.println("CGText "+location.toString()+" "+text);
 }
}


CGText declares one variable, text, that is used to store a string of text to be displayed on the grid. It has a single constructor that takes two arguments: a Point value that identifies the point on the grid where the text is to be displayed and a String value that specifies this text. Because drawCharacter is not displayed, it is initialized to a space. CGText implements the two abstract methods required of it. The display() method displays the text variable at the location specified by the location variable. The describe() method displays the location of the point and its text to the console window.

The KeyboardInput Class

The KeyboardInput class extends the DataInputStream class of the Java API to provide a set of common simple methods for getting keyboard input from the user. (See Listing 5.9.)


Listing 5.9. The KeyboardInput class source code.

package jdg.ch05;

import java.lang.System;
import java.io.DataInputStream;
import java.io.InputStream;
import java.io.IOException;

public class KeyboardInput extends DataInputStream {
 public KeyboardInput(InputStream inStream) {
  super(inStream);
 }
 public char getChar() throws IOException {
  String line = readLine();
  if(line.length()==0) return ' ';
  return line.charAt(0);
 }
 public String getText() throws IOException {
  String line = readLine();
  return line;
 }
 public int getInt() throws IOException {
  String line = readLine();
  Integer i = new Integer(line);
  return i.intValue();
 }
 public Point getPoint() throws IOException {
  System.out.print(" x-coordinate: ");
  System.out.flush();
  int x = getInt();
  System.out.print(" y-coordinate: ");
  System.out.flush();
  int y = getInt();
  return new Point(x,y);
 }
}


KeyboardInput has a single constructor that takes an object of class InputStream as a parameter. This object should be java.lang.System.in, but may be mapped to other input streams if necessary. The KeyboardInput constructor passes the InputStream object to DataInputStream's constructor using the super() constructor call.

KeyboardInput defines four access methods that get objects of type char, String, int, and Point from the user. The getChar() method uses the readLine() method of DataInputStream to read a line of data entered by the user. If the line is blank, it returns the space character; otherwise it returns the first character in the input line. The getText() method simply returns the entire line entered by the user, whether it is blank or not. The getInt() method works the same way as getChar() except that it uses a constructor of the Integer class to construct an object of class Integer directly from the input line. It then converts the Integer object to an object of type int before it returns it using the return statement. The Integer class is an example of a class that wraps the primitive type int. Class wrappers are covered in Chapter 12, "Portable Software and the java.lang Package."

The getPoint() method interacts with the user to get the x- and y-coordinates of a point. It then constructs an object of class Point that it uses as its return value. The getPoint() method uses the getInt() method to get the values of the x- and y-coordinates.

The CDrawApp Program

You're finally ready to use all the classes that you've developed in this chapter to build the CDrawApp program. Make sure that you've compiled all the classes that have been introduced. Your c:\java\jdg\ch05 directory should have compiled classes for Point.java, CGrid.java, CGObject.java, PrintCGrid.java, BorderedPrintCGrid.java, CGPoint.java, CGBox.java, CGText.java, and KeyboardInput.java. The CDrawApp.java file is shown in Listing 5.10.


Listing 5.10. The CDrawApp and CDraw classes.

package jdg.ch05;

import java.lang.System;
import java.io.DataInputStream;
import java.io.IOException;

class CDrawApp {
 public static void main(String args[]) throws IOException {
  CDraw program = new CDraw();
  program.run();
 }
}

class CDraw {
 // Variable declarations
 static KeyboardInput kbd = new KeyboardInput(System.in);
 BorderedPrintCGrid grid;

 // Method declarations
 CDraw() {
  grid = new BorderedPrintCGrid();
 }
 void run() throws IOException {
  boolean finished = false;
  do {
   char command = getCommand();
   switch(command){
    case 'P':
     addPoint();
     System.out.println();
     break;
    case 'B':
     addBox();
     System.out.println();
     break;
    case 'T':
     addText();
     System.out.println();
     break;
    case 'U':
     grid.deleteLastObject();
     System.out.println();
     break;
    case 'C':
     grid.clearGrid();
     System.out.println();
     break;
    case 'S':
     grid.show();
     break;
    case 'X':
     finished = true;
    default:
     System.out.println();
   }
  } while (!finished);
 }
 char getCommand() throws IOException {
  System.out.println("CDrawApp P - Add a Point U - Undo Last Add");
  System.out.print("Main Menu B - Add a Box C - Clear Grid");
  System.out.println(" X - Exit CDrawApp");
  System.out.print(" T - Add Text S - Show Grid");
  System.out.print(" Enter command: ");
  System.out.flush();
  return Character.toUpperCase(kbd.getChar());
 }
 void addPoint() throws IOException {
  System.out.println("Add Point Menu");
  System.out.println(" Location:");
  Point p = kbd.getPoint();
  System.out.print(" Character: ");
  System.out.flush();
  char ch = kbd.getChar();
  if(ch==' ') ch = '+';
  CGPoint cgp = new CGPoint(p,ch);
  cgp.addToGrid(grid);
 }
 void addBox() throws IOException {
  System.out.println("Add Box Menu");
  System.out.println(" Upper Left Corner:");
  Point ul = kbd.getPoint();
  System.out.println(" Lower Right Corner:");
  Point lr = kbd.getPoint();
  System.out.print(" Character: ");
  System.out.flush();
  char ch = kbd.getChar();
  if(ch==' ') ch = '#';
  CGBox box = new CGBox(ul,lr,ch);
  box.addToGrid(grid);
 }
 void addText() throws IOException {
  System.out.println("Add Text Menu");
  System.out.println(" Location:");
  Point p = kbd.getPoint();
  System.out.print(" Text: ");
  System.out.flush();
  String text = kbd.getText();
  CGText cgt = new CGText(p,text);
  cgt.addToGrid(grid);
 }
}


The declaration of the CDrawApp class is very small. It consists of a main() method that creates an object of class CDraw and then invokes the run() method for that object. A separate CDraw object is created because the main() method is static. static methods are like static variables. They apply to the class as a whole and not to objects that are individual instances of a class. static methods can only access variables that they create or that are static variables of the class. By creating the CDraw class, you are able to avoid any limitations posed by static's main() method.

The CDraw class declares two variables: kbd and grid. The kbd variable is used to get input from the user. The grid variable is used to display objects such as points, boxes, and text. The kbd variable is created as a static variable of class KeyboardInput. It is initialized using the KeyboardInput() constructor and the predefined java.lang.System.in input stream. A variable initializer is used to create and initialize the value of kbd. Other initializers, called static initializers, are also supported by Java. Static initializers allow blocks of statements to be executed during class initialization. They are covered in Chapter 11, "Language Summary."

CDraw has a single constructor that creates an object that is a new instance of BorderedPrintCGrid and assigns it to the grid variable. The BorderedPrintCGrid() constructor creates a default grid 75 characters wide and 20 characters high. The default border character is an asterisk (*).

CDraw has five access methods. The run() method implements the core processing of the CDrawApp program. It uses a do statement that repeatedly processes user keyboard commands. It invokes the getCommand() method to display a menu to the user and retrieve the user's command selection. It then uses a switch statement to process the command. It invokes the addPoint(), addBox(), and addText() methods to add points, boxes, and text to the grid. It invokes the deleteLastObject() and clearGrid() methods of the PrintCGrid class to remove the last object added to the grid or to completely clear the grid of all objects. The show() method of PrintCGrid is used to draw and display the grid. If the user enters a command line beginning with X or x, the boolean variable finished is set to true, the do statement finishes, and the CDrawApp program terminates.

The getCommand() method displays a menu to the user and uses the getChar() method of the KeyboardInput class to get a character command from the user. The static toUpperCase() method of the Character class is used to convert the character returned by getChar() to uppercase.

The addPoint() method queries the user to enter the location of a point and the character to be used to displayed at that location. It uses the getPoint() and getChar() methods of the KeyboardInput class. If a user enters a space for the display character, addPoint() uses the plus (+) character as a default. It uses the data obtained from the user to construct an object of class CGPoint and adds the object to grid using the addToGrid() method of class CGObject that is inherited by class CGPoint.

The addBox() method is similar to the addPoint() method except that it must obtain two points from the user-the upper-left and lower-right corners of a rectangle. It also obtains a box display character from the user, the default value of which is the pound (#) sign. An object of class CGBox is constructed from the user-supplied information and added to the grid using the CGObject addToGrid() method.

The final method of CDraw, addText(), retrieves a location and a text string from the user and uses this information to create an object of class CGText. The new object is then added to the grid in the same manner as the CGPoint and CGBox objects.

Running CDrawApp

Assuming that you have compiled all the classes introduced in this chapter, go ahead and compile CDrawApp. You can then run CDrawApp using the following command line:

C:\java\jdg\ch05>java jdg.ch05.CDrawApp

CDrawApp begins by displaying the following menu:

CDrawApp    P - Add a Point   U - Undo Last Add
Main Menu   B - Add a Box     C - Clear Grid      X - Exit CDrawApp
            T - Add Text      S - Show Grid       Enter command:

This menu provides you with seven command options: P, B, T, U, C, S, and X. Entering X will cause CDrawApp to terminate. You don't want to do this yet. Entering S will cause CDrawApp to display the character grid. Go ahead and enter S. Your screen should look like this:

***************************************************************************
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
***************************************************************************
CDrawApp    P - Add a Point   U - Undo Last Add
Main Menu   B - Add a Box     C - Clear Grid      X - Exit CDrawApp
            T - Add Text      S - Show Grid       Enter command:

CDrawApp displays a blank bordered grid using the show() method of the PrintCGrid class. You should be able to trace program execution up to this point by examining the source code files of the classes used in this chapter. The CDraw run() and getCommand() methods perform most of the user interface processing.

You can add a point to the grid by entering a P command. You will get the following display:

Add Point Menu
 Location:
  x-coordinate:

The Add Point Menu prompt is displayed by the addPoint() method of the CDraw class. It prompts you to enter the x-coordinate of a grid point. The upper-left corner of the grid is 0,0 and the lower-right corner of the grid is 74,19, where 74 is the maximum x-coordinate and 19 is the maximum y-coordinate. Enter 35 for the x-coordinate. The Add Point Menu then prompts you to enter the y-coordinate:

Add Point Menu
 Location:
  x-coordinate: 35
  y-coordinate:

Enter 10 for the y-coordinate. You are prompted to enter the character to be displayed at location 35,10. Enter x to finish adding a point:

Add Point Menu
 Location:
  x-coordinate: 35
  y-coordinate: 10
 Character: x
CDrawApp    P - Add a Point   U - Undo Last Add
Main Menu   B - Add a Box     C - Clear Grid      X - Exit CDrawApp
            T - Add Text      S - Show Grid       Enter command:

The CDrawApp main menu is displayed again. To verify that the point you just entered was, in fact, added to the grid, redisplay the grid by entering S. You will see the x in the middle of the grid:

***************************************************************************
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                   x       &n bsp;                               *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
*                                          &nb sp;                                *
***************************************************************************
CDrawApp    P - Add a Point   U - Undo Last Add
Main Menu   B - Add a Box     C - Clear Grid      X - Exit CDrawApp
            T - Add Text      S - Show Grid       Enter command:

Now use the B command to enter a box:

CDrawApp    P - Add a Point   U - Undo Last Add
Main Menu   B - Add a Box     C - Clear Grid      X - Exit CDrawApp
            T - Add Text      S - Show Grid       Enter command: b
Add Box Menu
 Upper Left Corner:
  x-coordinate:

You will have to enter two points and a character to specify a box. Enter 5 for the x-coordinate of the upper-left corner and 1 for its y-coordinate:

Add Box Menu
 Upper Left Corner:
  x-coordinate: 5
  y-coordinate: 1
 Lower Right Corner:
  x-coordinate:

Enter 70 for the x-coordinate of the lower-right corner and 18 for its y-coordinate:

Add Box Menu
 Upper Left Corner:
  x-coordinate: 5
  y-coordinate: 1
 Lower Right Corner:
  x-coordinate: 70
  y-coordinate: 18
 Character:

Finally, set the box character to the equals sign (=):

Add Box Menu
 Upper Left Corner:
  x-coordinate: 5
  y-coordinate: 1
 Lower Right Corner:
  x-coordinate: 70
  y-coordinate: 18
 Character: =
CDrawApp    P - Add a Point   U - Undo Last Add
Main Menu   B - Add a Box     C - Clear Grid      X - Exit CDrawApp
            T - Add Text      S - Show Grid       Enter command:

Go ahead and redisplay the grid using the ShowGrid command:

***************************************************************************
*                                          &nb sp;                                *
*     ==================================================================    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                             x        & nbsp;                         =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     ==================================================================    *
*                                          &nb sp;                                *
***************************************************************************
CDrawApp    P - Add a Point   U - Undo Last Add
Main Menu   B - Add a Box     C - Clear Grid      X - Exit CDrawApp
            T - Add Text      S - Show Grid       Enter command:

Notice how the box was added to the grid. Now, let's add text to the grid. Enter T to bring up the Add Text Menu prompt:

Add Text Menu
 Location:
  x-coordinate:

Set the x-coordinate to 36 and the y-coordinate to 11:

Add Text Menu
 Location:
  x-coordinate: 36
  y-coordinate: 11
 Text:

Enter I love Java. at the Text: prompt:

Add Text Menu
 Location:
  x-coordinate: 36
  y-coordinate: 11
 Text: I love Java.

The CDrawApp main menu is displayed. Use the Show Grid command to redisplay the grid:

***************************************************************************
*                                          &nb sp;                                *
*     ==================================================================    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                             x                                  =    *
*     =                             I love Java.                       =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     =                                     &n bsp;                          =    *
*     ==================================================================    *
*                                          &nb sp;                                *
***************************************************************************
CDrawApp    P - Add a Point   U - Undo Last Add
Main Menu   B - Add a Box     C - Clear Grid      X - Exit CDrawApp
            T - Add Text      S - Show Grid       Enter command:

Enter U to invoke the Undo Last Add command. This results in the text being deleted from the display. Verify this by redisplaying the grid. Then clear the grid by entering C. Once again, use the Show Grid command to verify that the command worked correctly.

You have now covered all the commands of CDrawApp. Enter X to exit the program.

CDrawApp's Implementation of Object-Oriented Concepts

The purpose of CDrawApp isn't to bolster your graphics production capabilities. It is used as a comprehensive example of how Java classes, objects, and methods can be used to implement the object-oriented programming concepts studied earlier in the chapter.

In building CDrawApp, you created 11 classes, 6 of which extended classes other than Object. The class hierarchy for the CDrawApp program is shown in Figure 5.4.

Figure 5.4 : The class hierarchy.

The development of the CGrid, PrintCGrid, and BorderedPrintCGrid classes shows how subclasses extend the data and methods inherited from their parents to successively add more features to their branch of the class hierarchy. CGrid provides the basic data and methods to implement character grid objects. PrintCGrid adds the capability to add and remove objects from the grid, and to display these objects on the console window. BorderedPrintCGrid uses the methods provided by CGrid and PrintCGrid to develop additional capabilities for displaying a bordered grid.

The CGObject, CGPoint, CGBox, and CGText classes are examples of how abstract classes are used to specify the behavior of their subclasses. CGObject provides the addToGrid() method, which is inherited by all of its subclasses. It defines the display() and describe() methods as abstract, requiring all subclasses to implement these methods in a manner that is applicable to the subclass. CGPoint, CGBox, and CGText define specific types of graphical objects that fit within the framework established by CGObject.

The drawGrid() method of PrintCGrid utilizes the CGObject class as an abstraction for dealing with objects in the displayList[]. The dynamic binding and polymorphic capabilities of the Java compiler and runtime system enable drawGrid() to interact with objects of subclasses of CGObject without specific knowledge of their class type.

The grid object of class BorderedPrintCGrid that is used in the CDraw class provides an example of advanced polymorphic behavior. When grid's show() method is invoked, the show() method of the PrintCGrid class is used. The show() method invokes the PrintCGrid drawGrid() method to cause each object in the displayList[] to display itself on the grid. It then invokes the displayGrid() method to display the grid. BorderedPrintCGrid overrides the displayGrid() method of PrintCGrid. Which displayGrid() method does show() invoke-that of PrintCGrid or BorderedPrintCGrid? The show() method is able to discern from runtime information that it is being invoked for an object of class BorderedPrintCGrid, so it uses the BorderedPrintCGrid displayGrid() method. This method checks to see if the useBorder variable is true, and if so, displays a grid with a border. However, if useBorder is false, it invokes the displayGrid() method of PrintCGrid.

The Point class is an example of encapsulation. It has a modular, well-defined interface and hides the details of its implementation from other classes. The x- and y-coordinates of a point are inaccessible to methods outside of the Point class. Even the methods within Point use the x() and y() methods to access these values. The Point class, because of this encapsulation, can be reused in many other applications that use two-dimensional grids.

The KeyboardInput class shows how classes from the Java API may be extended by user-defined subclasses. It uses the readLine() method from DataInputStream, the charAt() method from the String class, and the intValue() method of the Integer class to provide convenient methods for accessing user input lines.

The CDraw and CDrawApp classes illustrate the principle of object composition. They assemble the other classes defined in this chapter into a short, two-page program that uses all of the features provided by these classes.

Summary

In this chapter you have learned some general object-oriented programming concepts and how these concepts apply to Java programs. You have learned how to use classes to develop a sample Java program that illustrates the fundamental elements of Java object-oriented programming.

The classes you developed in this chapter will be reused in Chapters 6 and 7. In Chapter 6 you'll learn how to use interfaces to support features of multiple inheritance. In Chapter 7 you'll learn how to use exceptions to respond to errors and other anomalies during programm execution.