Object-oriented programming is at the core of Java. In fact, all Java programs are objectoriented—
this isn’t an option the way that it is in C++, for example. OOP is so integral
to Java that you must understand its basic principles before you can write even simple
Java programs. Therefore, this chapter begins with a discussion of the theoretical aspects
of OOP.
Two Paradigms
As you know, all computer programs consist of two elements: code and data. Furthermore,
a program can be conceptually organized around its code or around its data. That is,
some programs are written around “what is happening” and others are written around
“who is being affected.” These are the two paradigms that govern how a program is
constructed. The first way is called the process-oriented model. This approach characterizes
a program as a series of linear steps (that is, code). The process-oriented model can be
thought of as code acting on data. Procedural languages such as C employ this model to
considerable success. However, as mentioned in Chapter 1, problems with this approach
appear as programs grow larger and more complex.
To manage increasing complexity, the second approach, called object-oriented
programming, was conceived. Object-oriented programming organizes a program around
its data (that is, objects) and a set of well-defined interfaces to that data. An object-oriented
program can be characterized as data controlling access to code. As you will see, by switching
the controlling entity to data, you can achieve several organizational benefits.
Abstraction
An essential element of object-oriented programming is abstraction. Humans manage
complexity through abstraction. For example, people do not think of a car as a set of
tens of thousands of individual parts. They think of it as a well-defined object with its
own unique behavior. This abstraction allows people to use a car to drive to the grocery
store without being overwhelmed by the complexity of the parts that form the car. They
can ignore the details of how the engine, transmission, and braking systems work. Instead
they are free to utilize the object as a whole.
A powerful way to manage abstraction is through the use of hierarchical classifications.
This allows you to layer the semantics of complex systems, breaking them into more
manageable pieces. From the outside, the car is a single object. Once inside, you see
that the car consists of several subsystems: steering, brakes, sound system, seat belts,
heating, cellular phone, and so on. In turn, each of these subsystems is made up of more
specialized units. For instance, the sound system consists of a radio, a CD player, and/or
a tape player. The point is that you manage the complexity of the car (or any other
complex system) through the use of hierarchical abstractions.
The Three OOP Principles
All object-oriented programming languages provide mechanisms that help you implement
the object-oriented model. They are encapsulation, inheritance, and polymorphism.
Let’s take a look at these concepts now.
Encapsulation
Encapsulation is the mechanism that binds together code and the data it manipulates,
and keeps both safe from outside interference and misuse. One way to think about
encapsulation is as a protective wrapper that prevents the code and data from being
arbitrarily accessed by other code defined outside the wrapper. Access to the code
and data inside the wrapper is tightly controlled through a well-defined interface.
To relate this to the real world, consider the automatic transmission on an automobile.
It encapsulates hundreds of bits of information about your engine, such as how much
you are accelerating, the pitch of the surface you are on, and the position of the shift
lever. You, as the user, have only one method of affecting this complex encapsulation:
by moving the gear-shift lever. You can’t affect the transmission by using the turn signal
or windshield wipers, for example. Thus, the gear-shift lever is a well-defined (indeed,
unique) interface to the transmission. Further, what occurs inside the transmission does
not affect objects outside the transmission. For example, shifting gears does not turn
on the headlights! Because an automatic transmission is encapsulated, dozens of car
manufacturers can implement one in any way they please. However, from the driver’s
point of view, they all work the same. This same idea can be applied to programming.
The power of encapsulated code is that everyone knows how to access it and thus
can use it regardless of the implementation details—and without fear of unexpected
side effects.
In Java the basis of encapsulation is the class. Although the class will be examined
in great detail later in this book, the following brief discussion will be helpful now. A
class defines the structure and behavior (data and code) that will be shared by a set of
objects. Each object of a given class contains the structure and behavior defined by the
class, as if it were stamped out by a mold in the shape of the class. For this reason, objects
are sometimes referred to as instances of a class. Thus, a class is a logical construct; an
object has physical reality.
When you create a class, you will specify the code and data that constitute that
class. Collectively, these elements are called members of the class. Specifically, the data
defined by the class are referred to as member variables or instance variables. The code
that operates on that data is referred to as member methods or just methods. (If you are
familiar with C/C++, it may help to know that what a Java programmer calls a method,
a C/C++ programmer calls a function.) In properly written Java programs, the methods
define how the member variables can be used. This means that the behavior and interface
of a class are defined by the methods that operate on its instance data.
Inheritance
Inheritance is the process by which one object acquires the properties of another object.
This is important because it supports the concept of hierarchical classification. As
mentioned earlier, most knowledge is made manageable by hierarchical (that is, top-down)
classifications. For example, a Golden Retriever is part of the classification dog, which
in turn is part of the mammal class, which is under the larger class animal. Without the
use of hierarchies, each object would need to define all of its characteristics explicitly.
However, by use of inheritance, an object need only define those qualities that make it
unique within its class. It can inherit its general attributes from its parent. Thus, it is the
inheritance mechanism that makes it possible for one object to be a specific instance of
a more general case. Let’s take a closer look at this process.
Polymorphism
Polymorphism (from the Greek, meaning “many forms”) is a feature that allows one
interface to be used for a general class of actions. The specific action is determined by
the exact nature of the situation. Consider a stack (which is a last-in, first-out list). You
might have a program that requires three types of stacks. One stack is used for integer
values, one for floating-point values, and one for characters. The algorithm that
implements each stack is the same, even though the data being stored differs. In a non–
object-oriented language, you would be required to create three different sets of stack
routines, with each set using different names. However, because of polymorphism, in
Java you can specify a general set of stack routines that all share the same names.
More generally, the concept of polymorphism is often expressed by the phrase “one
interface, multiple methods.” This means that it is possible to design a generic interface
to a group of related activities. This helps reduce complexity by allowing the same
interface to be used to specify a general class of action. It is the compiler’s job to select the
specific action (that is, method) as it applies to each situation. You, the programmer, do
not need to make this selection manually. You need only remember and utilize the
general interface.