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.
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.
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.
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.
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 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.
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."
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.
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 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.
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.
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.
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 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)+")");
}
}
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.
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.
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 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 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 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 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 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 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 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 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.
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.
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.
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.
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