Java 101: Foundations

Java 101: Polymorphism in Java

Use subtype polymorphism to execute different forms of the same method

1 2 Page 2
Page 2 of 2
Exception in thread "main" java.lang.ClassCastException:
Superclass cannot be cast to Subclass
at BadDowncast.main(BadDowncast.java:17)

Runtime type identification

The JVM's cast verification in Listing 5 illustrates runtime type identification (or RTTI, for short). Cast verification performs RTTI by examining the type of the cast operator's operand to see whether the cast should be allowed or not. In this scenario, the cast should not be allowed.

Another form of RTTI involves the instanceof operator. This operator checks the left operand to see whether or not it's an instance of the right operand and returns true when this is the case. The following example introduces instanceof to Listing 5, to prevent the ClassCastException:

if (superclass instanceof Subclass)
{
   Subclass subclass = (Subclass) superclass;
   subclass.method();
}

The instanceof operator detects that variable superclass's instance was not created from Subclass and returns false to indicate this fact. As a result, the code that performs the illegal cast will not execute.

Because a subtype is a kind of supertype, instanceof will return true when its left operand is a subtype instance or a supertype instance of its right operand supertype. The following example demonstrates:

Superclass superclass = new Superclass();
Subclass subclass = new Subclass();
System.out.println(subclass instanceof Superclass); // Output: true
System.out.println(superclass instanceof Superclass); // Output: true

This example assumes the class structure shown in Listing 5 and instantiates Superclass and Subclass. The first System.out.println() method call outputs true because subclass's reference identifies an instance of a subclass of Superclass; the second System.out.println() method call outputs true because superclass's reference identifies an instance of Superclass.

Covariant return types

A covariant return type is a method return type that, in the superclass's method declaration, is the supertype of the return type in the subclass's overriding method declaration. I've created a small application that demonstrates this language feature. Check out Listing 6 for the source code.

Listing 6. Demonstrating covariant return types

class BaseReturnType
{
   @Override
   public String toString()
   {
      return "base class return type";
   }
}

class DerivedReturnType extends BaseReturnType
{
   @Override
   public String toString()
   {
      return "derived class return type";
   }
}

class BaseClass
{
   BaseReturnType createReturnType()
   {
      return new BaseReturnType();
   }
}

class DerivedClass extends BaseClass
{
   @Override
   DerivedReturnType createReturnType()
   {
      return new DerivedReturnType();
   }
}

public class CRTDemo
{
   public static void main(String[] args)
   {
      BaseReturnType brt = new BaseClass().createReturnType();
      System.out.println(brt);
      DerivedReturnType drt = new DerivedClass().createReturnType();
      System.out.println(drt);
   }
}

Listing 6 declares BaseReturnType and BaseClass superclasses and DerivedReturnType and DerivedClass subclasses. Each of BaseClass and DerivedClass declares a createReturnType() method. BaseClass's method has its return type set to BaseReturnType, whereas DerivedClass's overriding method has its return type set to DerivedReturnType, a subclass of BaseReturnType.

Covariant return types minimize upcasting and downcasting. For example, DerivedClass's createReturnType() method doesn't need to upcast its DerivedReturnType instance to its DerivedReturnType return type. Furthermore, this instance doesn't need to be downcast to DerivedReturnType when assigning to variable drt.

Compile Listing 6 as follows:

javac CRTDemo.java

Run the resulting application:

java CRTDemo

You should observe the following output:

base class return type
derived class return type

In the absence of covariant return types, you would end up with Listing 7.

Listing 7. Demonstrating the absence of covariant return types

class BaseReturnType
{
   @Override
   public String toString()
   {
      return "base class return type";
   }
}

class DerivedReturnType extends BaseReturnType
{
   @Override
   public String toString()
   {
      return "derived class return type";
   }
}

class BaseClass
{
   BaseReturnType createReturnType()
   {
      return new BaseReturnType();
   }
}

class DerivedClass extends BaseClass
{
   @Override
   BaseReturnType createReturnType()
   {
      return new DerivedReturnType();
   }
}

public class CRTDemo
{
   public static void main(String[] args)
   {
      BaseReturnType brt = new BaseClass().createReturnType();
      System.out.println(brt);
      DerivedReturnType drt =
         (DerivedReturnType) new DerivedClass().createReturnType();
      System.out.println(drt);
   }
}

In Listing 7, the first bolded code reveals an upcast from DerivedReturnType to BaseReturnType, and the second bolded code uses the required (DerivedReturnType) cast operator to downcast from BaseReturnType to DerivedReturnType before the assignment to drt.

In conclusion

Polymorphism lets you program in the abstract by creating uniform interfaces to different kinds of operands, arguments, and objects. In this article, you discovered subtype polymorphism, in which a type can serve as another type's subtype. You also learned that subtype polymorphism relies on upcasting and late binding; that classes that describe these uniform interfaces are declared abstract and can contain abstract methods; that downcasting relies on the cast operator and can result in ClassCastExceptions (which is why you can verify the target type via the instanceof operator -- a form of RTTI); and that covariant return types minimize upcasting and downcasting in a method return type context.

My next tutorial in this series will introduce you to class and object initialization.

1 2 Page 2
Page 2 of 2