Java automatically casts an object that belongs to a subclass to an object that
belongs to the superclass. The reverse is not true. However, an object that belongs
to the subclass can be explicitly cast to an object of type superclass. However, a
ClassCastException will be raised if the super object variable does not actually
reference an object of the subclass type.
For example, even if the FictionalCharacter class was not abstract, the following line
will produce a compilation error.
Superhero s = (Superhero) new FictionalCharacter () ;
The reason is that s references an object of type FictionalCharacter and therefore s
does not support variables such as goodPower . However, the syntax Superhero s implies
that the s object must have the variables goodPower and respect associated with it.
The second magical feature of our program is that the correct toString method is
executed for every fictional character of the ArrayList . Here is what happened. When
Java sees the syntax element.toString() , it determines the object that element refers
to. If element refers to an object of type Superhero , then the toString method from the
Superhero class is executed. Otherwise, if element refers to an object of type Villain ,
then the toString method from the Villain class is executed. This magical property of
always finding the right method to execute is called polymorphism . This term was chosen
because the toString method takes many forms and the correct version is always executed.
When the Java compiler sees the syntax o.m( ... ) ,where o is an object variable
and m is an instance method, the type of the o object is ignored. Java only considers
the object which o references. Suppose that this object belongs to the C class. If the C
class contains the m method with the correct parameters, then the method is executed.
Otherwise, Java searches in the superclass of the C class for the m method. The search
continues until Java reaches the root or inheritance hierarchy (more on this later).
Note that if neither the C class nor one of its ancestors contains the m method, then
the code o.m( ... ) will not compile. The reason is that the C class must be either
the class to which the o object belongs or one of its descendants in the inheritance
Let us examine the following code from the main method.
for (FictionalCharacter character : characters)
System.out. println(character . toString()) ;
We did not need to add the call to the method toString (it is automatically called
for the method println ), but we did so in order to make the example clearer. The vari-
able characters is an ArrayList of objects of type FictionalCharacter .Thevariable
character iterates over the elements of the ArrayList . Even though the type of the vari-
able character is FictionalCharacter , Java disregards this type when it tries to find
the correct method to execute. If the character object references a superhero, then the
toString method from the Superhero class is executed. Alternatively, if the character ob-
ject references a villain, then the toString method from the Villain class is executed. Note
that in order for the code to compile, the FictionalCharacter class or one of its super-
classes (or, in general, ancestors in the inheritance heterarchy) must contain the toString
method. However, this is not necessarily the toString method that will be executed.