|
|  |
Part II: Casting Objects
This is a short supplement to the article "Why Use Object-Oriented
Technologies?". It is designed to address the issue of why:
Shape shape = new Circle();
|
is a valid assignment.
Converting and Casting Between Primitive Datatypes
The Java language supports both primitive datatypes and
reference datatypes (objects and arrays). Primitive
datatypes are not defined in terms of other datatypes, and there
are eight primitive datatypes of different precisions in Java.
What happens when the value of a primitive datatype is
assigned to a variable of a different primitive datatype? Let's
say there are two numeric datatypes, short and int.
An integer datatype is larger than a short and let's attempt
the following assignment:
Is this valid, or not? It's valid because in assigning a datatype
with less precision to a datatype with more precision, we don't
lose any information. What if we reverse the process?
In trying to shoehorn a larger precision datatype into a smaller
precision datatype, we could potentially lose information, and
the above code won't compile in Java. The only way to circumvent
this at compile time is to use a cast operator in the
following way:
int i = 5;
short s = (short)i;
|
Now this will compile, but the results will be unpredictable
since you may lose data in the conversion. This seems like
common sense to most people.
Converting and Casting Between Object Types
In the article "Why Use Object-Oriented Technologies?", we
were building a graphical tool that manipulated different
shapes. For the object-oriented solution, we had created a
superclass called Shape, with a number of subclasses (e.g.
Circle, Square, Triangle, etc).
These classes can be treated as types; you can
create instances and store their references in variables,
e.g.:
Shape shape = new Shape();
Circle circle = new Circle();
|
However, that's not the end of the story when you use
inheritance. You could also do something like
this:
Shape shape = new Circle();
|
It turns out a variable can refer to an object of its own
type, or any of its subclasses. The Circle object returned
by the new operator is implicitly cast (this is known as
"upcasting")
to its superclass type before the assignment is done.
If you haven't seen this before, review what we discussed
about converting primitive types, look at the above object
assignment again and ask if this seems strange.
This should appear to be wrong initially, because a subclass
contains everything that it's superclass has, plus more.
This implies that a superclass is "smaller" than its
subclass, since it has less functionality. Well, if assigning
a larger datatype to a smaller datatype is dangerous because
we lose data, why is the above assignment valid?
How Upcasting Works
Upcasting is a fundamental concept which is critical to
building maintainable OO applications.
Assuming an object assignment can be treated the same
way as a primitive assignment is guaranteed to cause confusion.
What are the real questions we should be asking?
I like to view the problem this way.
First, look at the right hand side of the assignment. When
you say: "new anything", e.g. new Circle(), the internal
representation created is a Circle and will remain a Circle
until that object is destroyed.
What about on the left hand side? The type on the left is
like a surface contract; whatever methods are available for
that type, the internal representation must be able to
fulfill that surface contract.
Shape shape = new Shape();
|
Question: can the internal representation fulfill the
assigned type's contract? I would hope so, it's the same type.
Shape shape = new Circle();
|
How about this one?
The answer is yes, because a subclass has everything its
superclass has, plus more. So all subclasses can fulfill
the contract of a superclass, since they invariably have
more functionality than their superclass. This is safe.
Circle circle = new Shape();
|
How about this one?
Here, you're asking the superclass' internal representation
to fulfill the contract of a subclass, but the subclass
may have methods the superclass doesn't have, so this is
not safe.
In summary:
- Don't think of object assignment the same way you
think of primitive assignment. Size doesn't matter when
doing object assignment.
- The real question is: what is the internal
representation and can that internal representation fulfill
the surface contract? If so, the assignment is safe.
Why is this Important?
Without this understanding, the best you can do is build two endpoints
that talk to each other. When you understand how this concept works,
you can now build an endpoint that can talk to some future endpoint
that doesn't yet exist. This helps lessen the chances of one
development group being at a standstill until something is complete.
You can quickly scaffold things together and have them working even
if the details aren't filled in.

|  |
|
| Last updated: Wednesday Apr 09, 2008 @ 06:53 AM |