While most textbooks teach polymorphism by drawing UML diagrams,
the Brainy Draw project encourages you to draw. While others make up
hypothetical objects to use as examples for object serialization,
we'll discuss the real need to persist your objects. The Brainy Draw
project in this article is a fun way to learn polymorphism and object
serialization.
Polymorphism and object serialization are two important topics in
Java. While object serialization is easy to grasp once you understand
the concept of objects, polymorphism is sometimes a thorny
subject. This article first introduces you with a brief theory of both
topics, but won't spend too much time with theories, because there are
heaps of decent Java books that explain those topics in details. This
article, on the other hand, spends most of the space discussing the
application in which polymorphism and object serialization is
used. You can download the code and have a play with it. In this
Internet and PS2 era, I still believe in the old saying "practice
makes perfect."
Polymorphism
Polymorphism is one of the three fundamental principles in object
oriented programming. The other two are encapsulation and
inheritance. Of the three, I would say polymorphism is the hardest to
comprehend. To learn polymorphism, you should understand
inheritance.
Inheritance is a mechanism that allows a class to be defined as a
specialization of another class. It is said that the more specialized
class is a subclass or a child class of the more general class. The
class where the child class is derived from is also called the parent
class. Inheritance gets its name because the subclass inherits all the
properties and methods from the parent class, except those that are
private to the parent class. Why do we extend a class? For code
reuse; the structure and behavior already implemented by one class may
be used to define other classes. If someone else has already written a
class that satisfies 85% of your need, you can just extend the class
and save time by implementing only the remaining 15% of functionality.
Polymorphism is briefly described as "one interface, many
implementations." When dealing with class hierarchies, you often want
to treat an object not as the specific type that it is, but instead as
its base type. This allows you to write code that doesn't depend on
specific types.
This is often explained with the classic example,
Shape. Consider the class hierarchy in Figure 1.
Figure 1. The Shape base class is implemented by Rectangle, Oval and Line
In this hierarchy, Rectangle, Oval, and
Line are more specialized forms of
Shape. They share some basic behavior, though. If
Shape has a method called draw,
Rectangle, Oval, and Line
inherit this method. However, the draw method inherited
can be overridden in the child class. This method overriding is
actually what makes things more interesting.
Because Rectangle, Oval and
Line are derived from Shape, they are
themselves Shapes. Therefore you can write something like
the following code, for which the technical term is upcasting.
Shape shape = new Rectangle();
or
Shape shape = new Oval();
or
Shape shape = new Line();
It is legal to store a Rectangle object in a
Shape object reference because a Rectangle
is also a Shape. The other way around isn't true,
though. A Shape is not always a
Rectangle.
Now sometimes you have a Shape object reference
containing an object of a subclass of Shape, but you
don't know what exactly the object is, i.e. whether the object is a
rectangle, oval, or line. However, you can call the draw
method of Shape, and the correct implementation will be
called. In other words, if the object happens to be Oval,
the draw method in the Oval class is
called. If it is a Line, then the draw
method in Line is invoked.
Why this is very useful is explained better when you build the
implementation in the Brainy Draw project.
Object serialization
With object serialization, you can take any Java object that
implements the Serializable interface and turn it into a
sequence of bytes stored in a file or sent across a network. These
bytes can later be restored to regenerate the original object. This
feature provides a lightweight persistence mechanism for your
objects.
Object serialization also works across operating systems. This
means you can run a Java application on Windows and serialize an
object into a file. Later you can open this file on Linux and still
obtain the same object. If the object you serialized held references
to other objects, those other objects are serialized too, provided
they also implement the Serializable interface.
The good thing about object serialization is Java takes care of the byte
formats and ordering and all other details. This really saves a lot of
coding time.
To serialize an object, you need some sort of
OutputStream object that is wrapped inside an
ObjectOutputStream object. Afterwards, you can use the
writeObject method of the ObjectOutputStream
class to serialize your object by passing the object as a parameter to
the writeObject method.
To restore the object, you can reverse the process by wrapping an
InputStream object inside an
ObjectInputStream object. With this
ObjectInputStream object, you can call its
readObject method to obtain your object. What is returned
by the readObject method is an Object
object. You need to downcast it to the correct type to get the
original object.
As mentioned above, this article does not intend to elaborate on
theories. For more information, you are referred to Sun's Java site or many Java books
in the bookstore. You can, however, see object serialization in action
in the Brainy Draw project.
The Brainy Draw Project
The project that will highlight polymorphism and serialization in
this article is called Brainy Draw. This is a vector-based graphic
tool that allows you to draw different shapes of any size. An example
of another vector-based graphics application is Corel Draw. In this
type of drawing tool, each shape is an object that you paste on the
drawing area. Each object is an independent object whose properties
(thickness, color) can be changed and can be resized at any time
during and after the drawing process. The other type of graphics
application is a bitmap-based tool such as the Paint program in
Windows. In this kind of application, each object is converted into a
bitmap representation. Once you draw an object, the object blends with
the other objects already on the drawing board. If you save the
drawing and open it later, you won't be able to separate individual
objects from each other because they are now all bitmaps.
The sole purpose of Brainy Draw's existence is to demonstrate
polymorphism and object serialization in action. As such, Brainy Draw
is kept simple, as a learning tool should be. Nevertheless, it
satisfies the requirements of being called a graphics tool: Open New,
Open, Save, and Undo features, as well as a click and drag feature to
easily draw rectangles, ovals, and lines.
The three shapes demonstrate the use of polymorphism, and the Save
and Open features shows object serialization at work.
Figure 3 shows the project graphical interface and Listing 1 gives
the complete project code.
Figure
3. The Brainy Draw project.
Polymorphism: The Shape interface and three implementations
All the drawing shapes in Brainy Draw must implement the
Shape interface. The Shape interface has
only one method, the draw method. The signature of the
draw method is given in the interface listed below.
interface Shape extends Serializable {
public void draw(java.awt.Graphics g);
}
The draw method accepts one argument: an object
reference of type java.awt.Graphics. It is intended that
each class which implements Shape draws itself on this
Graphics object. More information on how to obtain and
use a Graphics object can be found in the "The Drawing
Board" section.