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

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

  1. The variable/reference type determines what methods CAN BE run.
  2. 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).

Comments

Comment on Changing reference types