The examples below assume an understanding of references and objects. See the Primitive types vs references exercises.
The examples below use the classes from Inheritance and polymorphism.
References and objects
Legal example
Pet p = new Cat(Color.BLACK);
The right side of the assignment operator (=
) creates an object/instance of type Cat
.
The left side of the =
creates a variable/reference of type Pet
.
A Cat
is a Pet
(Cat extends Pet
) so the statement is legal.
Example with compile time error
String str = new Dog("Clifford", "Big Red");
The right side of the =
creates an object of type Dog
.
The left side of the =
creates a variable of type String
.
A Dog
is not a String
so the statement results in a compile time error.
This is not a situation in which Java automatically runs toString
. It is possible to manually run toString
on the newly created Dog
object. This is not the same as refering to a Dog
object with a String
variable, which remains impossible.
It is also not possible to cast the Dog
to a String
. The examples below demonstrate this. Casting to a String
is not the same as running toString
.
Rules of polymorphism
- The variable/reference type determines what methods CAN BE run.
- The object/instance type determines what method IS ACTUALLY run. The most specific method possible is run.
Rule 1 example
Pet p = new Cat(Color.BLACK);
System.out.println(p.getName()); // line 1
System.out.println(p.getBreed()); // line 2
System.out.println(p.getColor()); // line 3
Line 1 is legal because getName
is in Pet
(the variable/reference type).
Line 2 and line 3 each result in the same compile time error because getBreed
and getColor
are not in Pet
.
Changing reference types
It is possible to get a reference of a different type to an existing object.
Example 1: From more specific type to less specific type
Cat c = new Cat(Color.BLACK);
System.out.println(c.getName());
System.out.println(c.getColor());
Pet p = c;
System.out.println(p.getName());
The variable p
stores a reference to (the memory address of) the same Cat
object as c
.
The statement Pet p = c;
does not require a cast. Any object to which c
can refer can also be refered to by p
.
It is uncommon to create more than one reference to the same object within the same scope. It is very common to have more than one reference to the same object, such as when a reference is passed to a method. See Changing reference types with method call.
Example 2: From less specific type to more specific type (works)
Pet p = new Cat(Color.BLACK);
System.out.println(p.getName());
Cat c = (Cat) p;
System.out.println(c.getName());
System.out.println(c.getColor());
A cast is a request for a reference of a different type to the same object. The cast succeeds if the object/instance type supports the requested reference type. The cast acknowledges the risk that the request might fail (see Example 4).
The variable c
is of type Cat
, which is more specific than Pet
. The cast is required because p
can refer to objects to which c
can not. For example, p
can refer to an object of type Dog
.
The Cat
reference allows the call to getColor
, which is not possible with the Pet
reference. Both p
and c
allow the call to getName
.
Example 3: Less specific to more specific (fails at compile time)
Pet p = new Cat(Color.BLACK);
Dog d = p; // line 1
Cat c = p; // line 2
Line 1 and line 2 each result in the same compile time error. p
can refer to objects that d
and c
can not.
Example 4: Less specific to more specific (crashes)
Pet p = new Cat(Color.BLACK);
Dog d = (Dog) p; // line 1
A cast is a request for a reference of a different type to the same object. The cast fails if the object/instance type does not support the requested reference type. The cast acknowledges the risk that the request might fail.
When a cast fails, it fails at run time (crashes). Line 1 compiles but throws a ClassCastException
(crashes) at run time.
Although casting reference types uses the same syntax as casting primitive types, the operations are not related. See Division operations and Working with char values for examples of casting primitive types.
Example 5: Less specific to more specific (might work, might crash)
Pet p = new Cat(Color.BLACK);
if(Math.random() < 0.5)
p = new Dog("Clifford", "Big Red");
Cat c = (Cat) p;
System.out.println(c.getName());
System.out.println(c.getColor());
The object/instance type is either Cat
or Dog
, depending on the random number generated. The condition evalutes to true
with a 50% probability. See Generate random numbers with Math.random().
If the object type is Cat
, the cast works and the code runs to completion.
If the object type is Dog
, the cast fails at run time with a ClassCastException
, as in Example 4.
Example 6: instanceof
operator
Pet p = new Cat(Color.BLACK);
if(Math.random() < 0.5)
p = new Dog("Clifford", "Big Red");
if(p instanceof Cat)
{
Cat c = (Cat) p;
System.out.println(c.getName());
System.out.println(c.getColor());
}
The instanceof
operator can be used to check if an object/instance type supports a specific reference type.
The code above is the same as in Example 5, except the cast and subsequent code are only executed if the object/instance type is Cat
(or a subclass).