Puzzling polymorphism question

J

James

I was playing around with a polymorphism today and I'm confused on how
I achieved my desired results. I know I shouldn't question if it
works, but I'd like to know why it works.

Given two classes, a Traveler and an Agent. Both inherit the abstract
Person class.

Login.class:

public static Person setLogin(int selector) {

Person person = null;

if (selector == 0) {
Traveler traveler = new Traveler();
traveler.setTravelerID("12345");

person = traveler;

} else {
Agent agent = new Agent();
agent.setAgentID("av333-33");

person = agent;
}

return person;
}

So when this method is called, it instantiates either an agent or
traveler and returns that object to the caller. But the return type
is Person.

TestLogin.class:

public static void main(String[] args) {
Traveler traveler = (Traveler)Login.setLogin(0);
System.out.println(traveler.getTravelerID());
}

The result printed is "12345". Which is correct.

Where I am confused is why the Traveler class properties were not lost
when it was assigned to a type Person in the Login.class [person =
traveler;]

Since the Person class does not have "travelerID", why would it not
have been chopped off?

Thanks for any assistance!

James
 
J

Joona I Palaste

James said:
I was playing around with a polymorphism today and I'm confused on how
I achieved my desired results. I know I shouldn't question if it
works, but I'd like to know why it works.
Given two classes, a Traveler and an Agent. Both inherit the abstract
Person class.

public static Person setLogin(int selector) {
Person person = null;
if (selector == 0) {
Traveler traveler = new Traveler();
traveler.setTravelerID("12345");
person = traveler;
} else {
Agent agent = new Agent();
agent.setAgentID("av333-33");
person = agent;
}
return person;
}
So when this method is called, it instantiates either an agent or
traveler and returns that object to the caller. But the return type
is Person.
TestLogin.class:

public static void main(String[] args) {
Traveler traveler = (Traveler)Login.setLogin(0);
System.out.println(traveler.getTravelerID());
}
The result printed is "12345". Which is correct.
Where I am confused is why the Traveler class properties were not lost
when it was assigned to a type Person in the Login.class [person =
traveler;]
Since the Person class does not have "travelerID", why would it not
have been chopped off?

Objects and variables are different things. You can assign an object to
any variables you like and it won't change the object at all. One object
can even be in several different variables at a time, each of different
types!
The object does not know or care what variables it has been assigned to.
It will always remain the same object.
Here is a quick rule of thumb I use myself: Objects have class.
Variables have type. You can assign an object to a variable if that
variable's type denotes the object's class, some of its superclasses, or
some interface it implements. Otherwise you can't.
 
P

P.Hill

James said:
Where I am confused is why the Traveler class properties were not lost
when it was assigned to a type Person in the Login.class [person =
traveler;]

Just adding to what Joona said:

What you are assigning are references to a variable which can hold such a
reference; you are assigning objects to a variable which can hold an object, so
when you eventually actually move the Person reference back to a variable of
type Traveler, it turns out you had a reference to a Traveler and all its
fields all along. The never went anywhere.

-Paul
 
D

Daniel Chirillo

The answer to your question has to do with upcasting and dynamic binding.

You're probably familiar with casting between related primitive types:

double pi = 3.14;
long bigWhole = (long)pi;

Look at the return type of setLogin(). It returns a reference to a Person.
Now look at the code inside the method. You have a variable of type Person,
which is being assigned a reference to either a Traveler or an Agent. The
method treats them both generically...as a Person (the class that Agent and
Traveler both inherit from). This is called upcasting ("up" because in UML
diagrams ancestor classes appear above their derived classes) and doesn't
require any special syntax. One of the magical features of polymorphism is
that even though setLogin() treats the object that is returned as a generic
Person, the specific type information is available at run-time. The
addresses of methods you call are not resolved until runtime (this is
dynamic binding).

Now look in main(): you call setLogin() and cast the returned reference to
a reference to a Traveler. This is downcasting and requires the standard
Java casting syntax. The compiler allows this, because it knows that
Traveler inherits from Person. This is why the traveler ID is not lost --
you are casting to a Traveler. If you hadn't downcasted, your code would
not have compiled if you tried to call getTravellerID():

Person p = Login.setLogin(0);
// Won't compile: p.getTravelerID());


Also note that if you pass in a non-zero value to setLogin(), it will return
an Agent and not a Traveler and you will get a ClassCastException at runtime
(you can cast up and down the class hierarchy but not horizontally).

Person p = Login.setLogin(1);
// Compiles, but throws a ClassCastException: ((Traveler)
p).getTravelerID());

Hope this helps.



James said:
I was playing around with a polymorphism today and I'm confused on how
I achieved my desired results. I know I shouldn't question if it
works, but I'd like to know why it works.

Given two classes, a Traveler and an Agent. Both inherit the abstract
Person class.

Login.class:

public static Person setLogin(int selector) {

Person person = null;

if (selector == 0) {
Traveler traveler = new Traveler();
traveler.setTravelerID("12345");

person = traveler;

} else {
Agent agent = new Agent();
agent.setAgentID("av333-33");

person = agent;
}

return person;
}

So when this method is called, it instantiates either an agent or
traveler and returns that object to the caller. But the return type
is Person.

TestLogin.class:

public static void main(String[] args) {
Traveler traveler = (Traveler)Login.setLogin(0);
System.out.println(traveler.getTravelerID());
}

The result printed is "12345". Which is correct.

Where I am confused is why the Traveler class properties were not lost
when it was assigned to a type Person in the Login.class [person =
traveler;]

Since the Person class does not have "travelerID", why would it not
have been chopped off?

Thanks for any assistance!

James
 
P

P.Hill

Daniel said:
This is called upcasting ("up" because in UML
diagrams ancestor classes appear above their derived classes) and doesn't
require any special syntax.

The term superclass is applied to ancestor classes, and the term subclass to
decendant classes, thus also suggesting a natural "UP" and "DOWN" regardless of
and a predecessor of any UML convention.

Note, also that the OP didn't ask why one could be assigned to the other, but
why he didn't lose anything in the process.

-Paul
 
E

Eric Sosman

James said:
I was playing around with a polymorphism today and I'm confused on how
I achieved my desired results. I know I shouldn't question if it
works, but I'd like to know why it works.

Given two classes, a Traveler and an Agent. Both inherit the abstract
Person class.
[When I treat a Traveler as a mere Person, why don't the
Traveler-specific things get lost?]

Others have given explanations of what Java is doing to
make this happen, but I'd like to offer an idea about *why*
Java is designed this way. Fundamentally, it's because that's
pretty much the way an object-oriented model must work if it's
to be of any use.

For example, you yourself can be thought of as an object
(no offense intended). You have various attributes: your
name, your birth date, your degree of dislike for jalapeño
ice cream, and so on. You have various behaviors: those
early-morning five-mile runs, the cute way you whimper when
you get killed again in Doom III, et cetera.

But in this exchange with comp.lang.java.programmer most
of your attributes and behaviors are irrelevant. In this role
you're just a Questioner, and possess only a few attributes:
your screen name, your Question, and little else. I (another
object) can interact with you purely as a Questioner -- but
when I do so, I do not and should not somehow eliminate your
other attributes and behaviors. You remain a James even if
I treat you only as a Questioner, or even if the person behind
the counter at Starbucks considers you purely as a Customer.

This is the way we expect real-world objects to behave.
Since the object-oriented disciplines get much of their
usefulness by appealing to our notions of how objects behave,
they need to be able to imitate this sort of thing with fairly
decent fidelity. So Java pretty much *must* make this happen;
it's more important to understand the necessity than to fret
about the implementation. IMHO.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top