Understanding Object-Oriented Programming
This chapter is a basic introduction to object-oriented programming. It introduces you to some of the basic concepts and terms you need to know as you get a handle on the specific details of how object-oriented programming works in Java.
Tip |
If you're more of a hands-on type, you may want to just skip this chapter and go straight to Book III, Chapter 2, where you find out how to create your own classes in Java. You can always return to this chapter later to review the basic concepts that drive object-oriented programming. Either way is okay by me. I get paid the same whether you read this chapter now or skip it and come back to it later. |
What Is Object Oriented Programming?
The term object-oriented programming means many different things. But at its heart, object-oriented programming is a type of computer programming based on the premise that all programs are essentially computer-based simulations of real-world objects or abstract concepts. For example:
- Flight-simulator programs attempt to mimic the behavior of real airplanes. Some do an amazingly good job; military and commercial pilots train on them. In the 1960s, the Apollo astronauts used a computer-controlled simulator to practice for their moon landings.
- Many computer games are simulations of actual games humans play-such as baseball, NASCAR racing, and chess. But even abstract games such as Pac Man or Final Fantasy 4 attempt to model the behavior of creatures and objects that could exist somewhere. Those programs simulate a conceptual game-one that can't actually be played anywhere in the real world but can by simulated by a computer.
- Business programs can be thought of as simulations of business processes-such as order taking, customer service, shipping, and billing. For example, an invoice isn't just a piece of paper; it's a paper that represents a transaction that has occurred between a company and one of its customers. Thus a computer-based invoice is really just a simulation of that transaction.
TECHNICAL STAUFF |
The notion of a programming language having a premise of this sort isn't new. Traditional programming languages such as C (and its predecessors, including even COBOL) are based on the premise that computer programs are computerized implementations of actual procedures-the electronic equivalent of "Step 1: Insert Tab A into Slot B." The LISP programming language is based on the idea that all programming problems can be looked at as different ways of manipulating lists. And the ever-popular database-manipulation language SQL views programming problems as ways to manipulate mathematical sets. |
Here are some additional thoughts about the notion of computer programs being simulations of real-world objects or abstract concepts:
- Sometimes the simulation is better than the real thing. Word-processing programs started out as simulations of typewriters, but a modern word-processing program is far superior to any typewriter.
- The idea that all computer programs are simulations of one type or another isn't a new one. In fact, the first object-oriented programming language (Simula) was developed in the 1960s. By 1967, this language had many of the features we now consider fundamental to object-oriented programming-including classes, objects, inheritance, and virtual methods.
- Come to think of it, manual business recordkeeping systems are simulations too. A file cabinet full of printed invoices doesn't hold actual orders. It holds written representations of those orders. A computer is a better simulation device than a file cabinet, but both are simulations.
Understanding Objects
All this talk of simulations is getting a little existential for me, so now I'm turning to the nature of the objects that make up object-oriented programming. Objects-both in the real world and in the world of programming-are entities that have certain basic characteristics. The following sections describe some of the more important of these characteristics: identity, type, state, and behavior.
Objects have identity
Every object in an object-oriented program has an identity. In other words, every occurrence of a particular type of object-called an instance-can be distinguished from every other occurrence of the same type of object, as well as from objects of other types.
In the real world, object identity is a pretty intuitive and obvious concept. Pick up two apples, and you know that although both of them are apples (that's the object type, described in the next section), you know they aren't the same apple. Each has a distinct identity. They're both roughly the same color, but not exactly. They're both roundish, but have minor variations in shape. Either one (or both) could have a worm inside.
Open a file cabinet that's full of invoices and you find page after page of papers that look almost identical to one another. However, each one has an invoice number printed somewhere near the top of the page. This number isn't what actually gives each of these invoices a unique identity, but it gives you an easy way to identify each individual invoice, just as your name gives others an easy way to identify you.
In object-oriented programming, each object has its own location in the computer's memory. Thus two objects, even though they may be of the same type, have their own distinct memory locations. The address of the starting location for an object provides us with a way of distinguishing one object from another, because no two objects can occupy the same location in memory.
Here are a few other important thoughts about object identity in Java:
- Java pretty much keeps each object's identity to itself. In other words, there's no easy way to get the memory address of an object. Java figures that's none of your business, and rightfully so. If Java made that information readily available to you, you'd be tempted to tinker with it, which can cause all sorts of problems as any C or C++ programmer can tell you.
- Java objects have something called a hash code, which is an int value that's automatically generated for every object and almost represents the object's identity. In most cases, the hash code for an object is based on the object's memory address. But not always. Java doesn't guarantee that two distinct objects won't have the same hash code.
- When used with objects, the equality operator (==) actually tests the object identity of two variables or expressions. If they refer to the same object instance, then the two variables or expressions are considered equal.
Objects have type
I remember studying Naming of Parts, a fine poem written by Henry Reed in 1942, back when I was an English major in college:
Today we have naming of parts. Yesterday,
We had daily cleaning. And tomorrow morning,
We shall have what to do after firing. But today,
Today we have naming of parts. Japonica
Glistens like coral in all of the neighboring gardens,
And today we have naming of parts.
Sure, it's a fine anti-war poem and all that, but it's also a little instructive about object-oriented programming. After the first stanza, the poem goes on to name the parts of a rifle:
This is the lower sling swivel. And this
Is the upper sling swivel, whose use you will see,
When you are given your slings. And this is the piling swivel,
Which in your case you have not got.
Imagine a whole room of new soldiers taking apart their rifles, while the drill sergeant tells them "This is the lower sling swivel. And this is the upper sling swivel…." Each soldier's rifle has one of these parts-in object-oriented terms, an object of a particular type. The lower-sling swivels in each soldier's rifle are different objects, but all are of the type LowerSlingSwivel.
Like the drill sergeant in this poem, object-oriented programming lets you assign names to the different kind of objects in a program. In Java, types are defined by classes. So when you create an object from a type, you're saying that the object is of the type specified by the class. For example, the following statement creates an object of type Invoice:
Invoice i = new Invoice();
In this case, the identity of this object (that is, its address in memory) is assigned to the variable i, which the compiler knows can hold references to objects of type Invoice.
Objects have state
Now switch gears to another literary genius:
One fish, two fish,
Red fish, blue fish
In object-oriented terms, Dr. Seuss here is enumerating a pair of objects of type Fish. The Fish type apparently has two attributes-call them number and color. These two objects have differing values for these attributes:
Attribute |
Object 1 |
Object 2 |
---|---|---|
Number |
One |
Two |
Color |
Red |
Blue |
The type of an object determines what attributes the object has. Thus, all objects of a particular type have the same attributes. However, they don't necessarily have the same values for those attributes. In this example, all Fish have attributes named Number and Color, but the two Fish objects have different values for these attributes.
The combination of the values for all the attributes of an object is called the object's state. Unlike its identity, an object's state can and usually does change over its lifetime. For example, some fish can change colors. The total sales for a particular customer changes each time the customer buys another product. The grade-point average for a student changes each time a new class grade is recorded. And the address and phone number of an employee change if the employee moves.
Here are a few more interesting details about object state:
- Some of the attributes of an object are publicly known, but others can be private. The private attributes may be vital to the internal operation of the object, but no one outside of the object knows they exist. They're like your private thoughts: They affect what you say and do, but nobody knows them but you.
- In Java, the state of an object is represented by class variables, which are called fields. A public field is a field that's declared with the public keyword so the variable can be visible to the outside world.
Objects have behavior
Another characteristic of objects is that they have behavior, which means they can do things. Like state, the specific behavior of an object depends on its type. But unlike state, the behavior isn't different for each instance of a type. For example, suppose all the students in a classroom have calculators of the same type. Ask them all to pull out the calculators and add two numbers-any two numbers of their choosing. All the calculators display a different number, but they all add in the same way-they all have a different state but the same behavior.
Another way to say that objects have behavior is to say they provide services that can be used by other objects. You've already seen plenty of examples of objects that provide services to other objects. For example, objects created from the NumberFormat class provide formatting services that turn numeric values into nicely formatted strings such as $32.95.
In Java, the behavior of an object is provided by its methods. Thus, the format method of the NumberFormat class is what provides the formatting behavior for NumberFormat objects.
Here are a few other notable points about object behavior:
- The interface of a class is the set of methods and fields that the class makes public so other objects can access them.
- Exactly how an object does what it does can and should be hidden within the object. Someone who uses the object needs to know what the object does, but doesn't need to know how it works. If you later find a better way for the object to do its job, you can swap in the new improved version without anyone knowing the difference.
The Life Cycle of an Object
As you work with objects in Java, understanding how objects are born, live their lives, and die is important. This topic is called the life cycle of an object, and it goes something like this:
- Before an object can be created from a class, the class must be loaded. To do that, the Java runtime locates the class on disk (in a .class file) and reads it into memory. Then Java looks for any static initializers that initialize static fields-fields that don't belong to any particular instance of the class, but rather belong to the class itself and are shared by all objects created from the class.
A class is loaded the first time you create an object from the class or the first time you access a static field or method of the class. For example, when you run the main method of a class, the class is initialized because the main method is static.
- An object is created from a class when you use the new keyword. To initialize the class, Java allocates memory for the object and sets up a reference to the object so the Java runtime can keep track of it. Then, Java calls the class constructor, which is like a method but is called only once, when the object is created. The constructor is responsible for doing any processing required to initialize the object, such as initializing variables, opening files or databases, and so on.
- The object lives its life, providing access to its public methods and fields to whoever wants and needs them.
- When it's time for the object to die, the object is removed from memory and Java drops its internal reference to it. You don't have to destroy objects yourself. A special part of the Java runtime called the garbage collector takes care of destroying all objects when they are no longer in use.
Working with Related Classes
So far, most of the classes you've seen in this book have created objects that stand on their own, each being a little island unto itself. However, the real power of object-oriented programming lies in its ability to create classes that describe objects that are closely related to each other.
For example, baseballs are similar to softballs. Both are specific types of balls. Each has a diameter and a weight; both can be thrown, caught, or hit. However, they have different characteristics that cause them to behave differently when thrown, caught, or hit.
If you're creating a program that simulated the way baseballs and softballs work, you need a way to represent these two types of balls. One option is to create separate classes to represent each type of ball. These classes are similar, so you can just copy most of the code from one class to the other.
Another option is to use a single class to represent both types of balls. Then, you pass a parameter to the constructor to indicate whether an instance of the class behaves like a baseball or like a softball.
However, Java has two object-oriented programming features that are designed specifically to handle classes that are related like this: inheritance and interfaces. I briefly describe these features in the following sections.
Inheritance
Inheritance is an object-oriented programming technique that lets you use one class as the basis for another. The existing class is called the base class, superclass, or parent class; the new class that's derived from it is called the derived class, subclass, or child class.
When you create a subclass, the subclass is automatically given all the methods and fields defined by its superclass. You can use these methods and fields as is, or you can override them to alter their behavior. In addition, you can add additional methods and fields that define data and behavior that's unique to the subclass.
You could use inheritance to solve the baseball/softball problem from the previous section by creating a class named Ball that provides the basic features of all types of balls, and then using it as the base class for separate classes named BaseBall and SoftBall. Then, these classes could override the methods that need to behave differently for each type of ball.
One way to think of inheritance is as a way to implement is-a-type-of relationships. For example, a softball is a type of ball, as is a baseball. Thus inheritance is an appropriate way to implement these related classes. (For more information about inheritance, see Book III, Chapter 4.)
Interfaces
An interface is a set of methods and fields that a class must provide to implement the interface. The interface itself is simply a set of public method and field declarations that are given a name. Note that the interface itself doesn't provide any code that implements those methods. Instead, it just provides the declarations. Then, a class that implements the interface provides code for each of the methods the interface defines.
You could use an interface to solve the baseball/softball problem by creating an interface named Ball that specifies all the methods and fields that a ball should have. Then, you could create the SoftBall and BaseBall classes so that they both implement the Ball interface.
Interfaces are closely related to inheritance, but have two key differences:
- The interface itself doesn't provide code that implements any of its methods. An interface is just a set of method and field signatures. In contrast, a base class can provide the implementation for some or all of its methods.
- A class can have only one base class. However, a class can implement as many interfaces as necessary.
You find out about interfaces in Book III, Chapter 5.
Designing a Program with Objects
An object-oriented program usually isn't just a single object. Instead, it's a group of objects that work together to get a job done. The most important part of developing an object-oriented program is designing the classes that are used to create the program's objects. The basic idea is to break a large problem down into a set of classes that are each manageable in size and complexity. Then, you write the Java code that implements those classes.
So the task of designing an object-oriented application boils down to deciding what classes the application requires-and what the public interface to each of those classes should be. If you plan your classes well, implementing the application is easy. But if you poorly plan your classes, you'll have a hard time getting your application to work.
One common way to design object-oriented applications is to divide the application into several distinct layers or tiers that provide distinct types of functions. The most common is a three-layered approach, as shown in Figure 1-1. Here the objects of an application are split up into three basic layers:
- Presentation: The objects in this layer handle all the direct interaction with users. For example, the HTML pages in a Web application go in this layer, as do the Swing page and frame classes in a GUI-based application (I cover Swing in Book VI).
- Logic: The objects in this layer represent the core objects of the application. For a typical business-type application, this layer includes objects that represent business entities such as customer, products, orders, suppliers, and the like. This layer is sometimes called the business rules layer because the objects in this layer are responsible for carrying out the rules that govern the application.
- Database: The objects in this layer handle all the details of interacting with whatever form of data storage is used by the application. For example, if the data is stored in a standard SQL database, the objects in this layer handle all of the SQL.
Figure 1-1: Three-layered design.
Diagramming Classes with UML
Since the very beginning of computer programming, programmers have loved to create diagrams of their programs. Originally, they drew flowcharts that graphically represented a program's procedural logic.
Flowcharts were good at diagramming procedures, but they were way too detailed. When the Structured Programming craze hit in the 1970s and programmers started thinking about the overall structure of their programs, they switched from flowcharts to structure charts, which illustrated the organizational relationships among the modules of a program or system.
Now that object-oriented programming is the thing, programmers draw class diagrams to illustrate the relationships among the classes that make up an application. For example, the simple class diagram shown in Figure 1-2 shows a class diagram for a simple system that has four classes. The rectangles represent the classes themselves, and the arrows represent the relationships among the classes.
Figure 1-2: A simple class diagram.
You can draw class diagrams in many ways. To add some consistency to their diagrams, most programmers use a standard called UML, which stands for Unified Modeling Language. The class diagram in Figure 1-2 is an example of a simple UML diagram, but UML diagrams can get much more complicated than this example.
The following sections describe the details of creating UML class diagrams. Note that these sections don't even come close to explaining all the features of UML. I include just the basics of creating UML class diagrams so that you can make some sense of UML diagrams when you see them, and so that you know how to draw simple class diagrams to help you design the class structure for your applications. If you're interested in digging deeper into UML, check out UML 2 For Dummies by Michael Jesse Chonoles and James A. Schardt (Wiley Publishing, Inc.).
Drawing classes
The basic element in a class diagram is a class. In UML, each class is drawn as a rectangle. At the minimum, the rectangle must include the class name. However, you can subdivide the rectangle into two or three compartments that can contain additional information about the class, as shown in Figure 1-3.
Figure 1-3: A class.
The middle compartment of a class lists the class variables, while the bottom compartment lists the class methods. The name of each variable or method can be preceded by a visibility indicator, which can be one of the symbols listed in Table 1-1. (In actual practice, it's common to omit the visibility indicator and list only those fields or methods that have public visibility.)
Indicator |
Description |
---|---|
+ |
Public |
- |
Private |
# |
Protected |
If you want, you can include type information for variables as well as for methods and parameters. The type of a variable is indicated by following the variable name with a colon and the type:
connectionString: String
A method's return type is indicated in the same way:
getCustomer(): Customer
Parameters are listed within the parentheses, and both the name and type are listed. For example:
getCustomer(custno: int): Customer
Note |
Omitting the type and parameter information from UML diagrams is common. |
Tip |
Interfaces are drawn pretty much the same as classes, but the class name is preceded by the word interface: «interface» ProductDB |
Note |
The word interface is enclosed within a set of double-left and double-right arrows. These arrows aren't just two less-than or greater-than symbols typed in a row; they're a special combination of symbols. Fortunately, the double-arrow symbol is a standard part of the ASCII character set. You can access it in Microsoft Word via the Insert Symbol command. |
Drawing arrows
Besides rectangles to represent classes, class diagrams also include arrows to represent relationships among classes. UML uses a variety of different types of arrows, as I describe in the following paragraphs.
A solid line with a hollow closed arrow at one end represents inheritance:
The arrow points to the base class. A dashed line with a hollow close arrow at one end indicates that a class implements an interface:
The arrow points to the interface. A solid line with an open arrow indicates an association:
An association simply indicates that two classes work together. It may be that one of the classes creates objects of the other class, or that one class requires an object of the other class to perform its work. Or perhaps instances of one class contain instances of the other class.
You can add a name to an association arrow to indicate its purpose. For example, if an association arrow indicates that instances of one class create objects of another class, you can place the word Creates next to the arrow.