| |||
Object-Oriented Programming | |||
|
Last lecture we looked at objects that had storage but no behavior. In essence they
were just a rich data type (what some languages refer to as a structure or
record) for storing several pieces of related data together.
In this lecture we will look at objects in their full glory, with both storage and behavior.
We will finally begin to see what object-oriented programming is all about.
The idea in object-oriented programming is to turn the traditional view of programming
(of breaking down a procedure into sub-procedures according to top-down design)
on its ear. Instead of focusing on tasks, we focus on the different sorts of things (or,
in the terminology, objects) that the program deals with. Thinking about those objects as
active participants in the program, we ask what behaviors they should have. Finally, we
attach those behaviors to the individual objects by adding them to the class definitions.
A complete program consists of the individual object class definitions,
together with a driver object that you invoke to run the overall program.
You can think of the driver object as the
| |||
A Non Object-Oriented Example | |||
Lets look at a simple example. Consider a program which will do some simple geometric and
trigonometric calculations. We expect that this program will frequently need to manipulate
points on the plane. Some of the things we want the
program to be able to do are:
Rather than always passing around the x and y coordinates in separate variables, we could use the ideas presented in the last lecture and build a point object that has two fields, as in:
Here is a program that uses this definition to do the things we want:
| |||
An Object-Oriented Example | |||
Another way of looking at the problem is to think in terms of the behaviors a point object
should have in order to be useful in the larger program. Some desired behaviors (based on the
methods we wrote in the last example) are:
These behaviors are added as methods to the definition of the
| |||
The first thing to notice is that after thirteen weeks of beginning all our
methods with that magical incantation public static, these method
definitions do not include the keyword static. The reason is that
static methods (and data fields) belong to the class, whereas
methods without that restriction belong to the individual objects of the
class that we create. For a method to be able to access the fields of an individual
object it must not be a static method. The use of static methods and fields
within class definitions for real classes is beyond the scope of this course.
| |||
Notice that the first method does not take any parameters, and yet (even
though it also doesn't declare any local variables) it refers to the variables
x and y. That is because these methods will always, implicitly, be operating on
a particular object of the Point class. Each object of that class can be thought of as though it
had its own copy of each of the methods. The methods, in turn, have direct access to the
values stored in the fields of the object they are attached to.
Thus, when the first method refers to
the variables x and y, it means the values of those fields in
the particular Point object to which this copy of the method belongs.
In the second method, references to x and y refer to the fields
of the Point object receiving the message, whereas p.x and p.y
refer to the fields of the Point object p that the
method receives as a parameter.
| |||
| Invoking A Method By Sending A Message |
As we have said, the behaviors defined by an object's methods are connected to the
individual objects of the class. In order to invoke a method attached to an object,
we send the object an appropriate message. This is done by writing the
name of the object we want to send the message to, a dot (or period), and the name of the
method we want to invoke, together with any parameters to the method. So, for example,
if we have the following variable declaration and initialization:
then if we wish to find the distance of that point from the origin, we might write:Point p = new Point(); p.x = 3.4; p.y = -2.35; That is, we are askingdouble d = p.distanceToOrigin(); Point object p "What is your distance from the origin?".
The method computes the answer by looking at the coordinates of the Point
object it belongs to. Similarly, if we also had the declaration/definition:
then if we wished to find the distance between the two points, we could write:Point p1 = new Point(); p1.x = 4.4; p1.y = 4.4; double d1 = p.distanceToPoint(p1); Or, since two points are equidistant from one another in standard geometry, we could also write: double d2 = p1.distanceToPoint(p);
| ||
| Constructor Methods |
In many, if not most, instances, the first thing you will want to do after you
create a new object is set the values of some of its fields, as we did above with the
Point object p. To make this easier, Java allows you to provide a
constructor method as part of your class definition.
The constructor is called each time a new object of the given class is created
with a call to new. Parameters are sent to the constructor by putting
them in the parentheses after the type name in the call to new
(which explains why we put the parentheses there in the first place).
The constructor method definition is, by standard agreement, the first method listed
in the class definition. Its header has a special form: the name of the class
is used as the name of the method, and there are no keywords like
With this method in place, we can replace the three lines we used to create the
Point object p above with:
Point p = new Point(3.4,-2.35); Note that the constructor method is not limited to setting the values of the objects fields. It can contain any code that you want to have executed that is appropriate to setting up the object for use. This code will be executed once for each object created from that class. Even if you are only using objects in the way they were described in the last lecture (with just storage, and no behavior) it is useful, and good style, to provide constructors for your objects.
Note that with constructor methods available, it becomes tempting, and convenient, to create
objects on the fly for short-term use. For example, the distance of a point from the origin is
really just a special case of the distance from a point to another point, where that other point
is at the origin. Using the constructor for
| ||
| Multiple Constructors |
It is actually possible to define more than one constructor method for an object.
When the object is created the constructor chosen is the one whose formal parameters
best match the parameters given in terms of types. For example, we could have one constructor
for student records that takes two strings (presumably the first and last name) and
another that takes an integer (the student id).
The most common use of having multiple constructors is to define one constructor for the
case that the user does not specify any initializing values in the call to In general it is considered better style to use default constructors than to assign initialization values to the objects fields at the point the fields are defined.
Note that once you define any constructors for a class, the default constructor (the one that
takes no parameters and simply allocates space on the heap for the object) is lost.
This way Java allows you to require that the user use the constructors you define, and not just use
the default constructor. If we wanted a default constructor for the class
| ||
| Method Overloading |
Actually, having multiple constructor methods for a method is just a specific example of a much
more general concept in Java called overloading. Any method you define in Java
can have multiple definitions distinguished by the types of their parameters. For example,
we could define several versions of the max method which returns the larger of its
two arguments. This saves us from having to call them each by a different name, even though,
logically, they all do the same thing. The different versions would differ only in the declared types of their parameters, and how they deal with the parameters to determine which is the larger one. We could include all of the following definitions of
this method in one program:
| ||
| Finalizer Methods |
Just as the constructor is called each time an object is created, a class can define a finalizer
method to be called each time an object of the class is destroyed. This method always has the name
finalize and has no parameters and no return value. When are objects destroyed?
Well, whenever a method exits, any objects that were created in the method that do not have a reason
to continue to exist (like they are being returned from the method) are destroyed. Any time it is
discovered that there is no longer any way for any part of the program to refer to an object, it is destroyed
in a process known as garbage collection. The objects we have used as examples have no
particular need for finalizer methods, so we will not show any actual examples. A finalizer method
is typically needed when an object has taken hold of some system resource that should be released
before the object is destroyed. For example, an object that writes into a file on disk should have
a finalizer method that ensures that the file is closed before the object is destroyed. Otherwise
other processes will not be able to access the file.
| ||
The Special Identifier this |
The use of the names x1 and y1 in the first Point constructor above was a bit
awkward and artificial. It would be much more natural to have those parameters named
x and y instead. Unfortunately this raises a problem. Having parameters
(or local variables) with those names would block access to the object's fields (which have the same names) from within the
method. A reference to x within the method would be a reference to the parameter
x of the method, not the field x of the object.
We can't refer to the field p.x in the method
because the name p does not exist in the context of the method. The name p
is just the name some other method is referring to this object by.
Fortunately, Java, and every other object-oriented language, provides a solution to this problem.
Within the methods of an object, the object to which the method belongs can be referred to by the
special name
Note that the identifier this is available in all of an object's method, not just the
constructor method.
The identifier
The solution is that inside a constructor, if the name
So, we can rewrite the default constructor for the
| ||
| Putting It All Together |
Putting all these ideas together, here is our final definition of the Point
class.
| ||
Specializing A Class Via Inheritance | |||
|
One of the great strengths of object oriented programming is what is known as inheritance,
the ability to define one class as a subclass of another class. The subclass inherits
the fields and methods of the superclass which it extends. The subclass
can augment the storage and behaviors of the superclass by adding additional fields and
methods. It can also override the behavior of the superclass by redefining methods
already defined in the superclass. The notion of inheritance is a natural one. Much of our thought process is based on constructing hierarchical taxonomies of the objects in our world: motor vehicles are a special case (or subclass) of vehicles, cars are a subclass of motor vehicles, and limousines are a subclass of cars. We infer some of the properties of a limousine from what we know about motor vehicles in general, some from what we know about cars, and some from specific properties of limousines. From a programming standpoint, inheritance provides a rich means of organizing code. Suppose we are writing a program to manage the accounts at a bank. We could begin by defining a fairly generic class of account objects. Such an object would have a field for the balance, a field for the account number, and some other fields for information such as the account owner's name, the date the account was opened, and other such bits of information. The class would also define methods for executing a withdrawal and a deposit. A savings account is just a special kind of account that earns interest. Thus it can be defined as a subclass of the account class. All that has to be added is a field for the interest rate, and a method for crediting an interest payment. A checking account could similarly be defined as a subclass of account.
| |||
A Subclass of Point |
Looking at our Point example, consider a variant of points with an odd behavior: they
live in a world where all lines are parallel to either the x or y axis; no diagonal lines
are allowed. Thus the distance from the origin is the x value plus the y value, not the length
of the straight line to the origin, since that may not be drawn. This is known as the
taxicab metric world, because it is the constraint operating on cabs driving around
on a rectalinear grid of city streets.
Taxicab points can be defined as a subclass of our existing
The definition of
| ||
The Special Identifier super |
Sometimes, it becomes useful for a subclass to access the behaviors of the superclass, even after
those behaviors have been overriden. For example, suppose we want a TaxicabPoint
to have a new method which returns its "true" (i.e. straight-line) distance to the origin.
This behavior is already available in the method distanceToOrigin so it would be
silly to reproduce that code. Unfortunately, that name has been redefined in this subclass
to compute the taxicab-distance, so it seems out of reach.
The solution is an identifier named
Thus we could add the following method to the class definition for A common situation is that the redefinition of the behavior of a method in a subclass involves adding some extra steps to the existing behavior. This is implemented by having the new definition perform the extra steps, at which point it then calls the version of the method in the superclass. I.e.:
A particularly common use of and then filling in the fields manually. Unfortunately, the subclass does not inherit the constructors form the parent class, so we can't just write:TaxicabPoint tcp = new TaxicabPoint(); toallocate the object and fill in its fields automatically. We could write the following:TaxicabPoint tcp = new TaxicabPoint(2.3,4.2); but this code would not compile, since in general aTaxicabPoint tcp = new Point(2.3,4.2); Point is not always
a TaxicabPoint, so we can't assign a Point object
(as created by the call to new) to a TaxicabPoint
object. To make this work, we would need to use an explicit cast, as in:
which is a bit awkward.TaxicabPoint tcp = (TaxicabPoint) new Point(2.3,4.2);
It is therefore better to define new constructors in each subclass.
However, most of the time these constructors will base their behavior on the superclass's
constructors. All we want to do is the same thing our superclass does,
but returning a member of this class. We can use
(Of course, we could have just given the code for these constructors directly, instead of appealing
to the behavior of the superclass. But this is generally the prefered way of doing things.
This way, if we decide to change the behavior of the constructors for all the point classes,
it only needs to be changed in the root Point class. Any subclasses will reflect the change
in their behavior automatically.
Here is the complete code for the
| ||
| Putting It All Together Again |
Here is a simple driver program that demonstrates the behavior of the Point
and TaxicabPoint classes:
Notice that the two points in the above program report themselves to be different distances
from one another, since they use different methods to compute that distance. A key feature
is that even though a It is important to remember that this is just a simple example. The methods and fields of an object can be as complex as we desire. In addition, we may use the objects we define in any way that we can use a built in type. we can make arrays of them, and we can use them as the fields of other objects. We can pass them as arguments to methods and return them as the return values of methods.
| ||
Last modified August 28 for Fall 99 cs5 by fleck@cs.hmc.edu
This page copyright ©1997 by Joshua S. Hodas. It was built with Frontier on a Macintosh . Last rebuilt on Sun, Dec 7, 1997 at 10:39:46 AM. | |
http://www.cs.hmc.edu/~hodas/courses/cs5/week_14/lecture/lecture.html | |