In object-oriented programming, polymorphism is the ability of an operation to behave differently depending on the class of the object involved. The main forms are:

  • Ad hoc polymorphism (or overloading): methods with the same name can have different parameters, and a call selects the compatible version based on its arguments;
  • Parametric polymorphism: generic classes and methods work with types that are specified when they are used;
  • Subtype polymorphism: a subclass can override a method, and the implementation that runs depends on the concrete class of the object.

The following examples show these three forms in Java, starting with overloading. The Figure class has two overloaded methods named resize. The first receives a percentage of type Double; the second receives two coordinates, x and y, of type Integer.

public class Figure {
    public void resize(Double percentage) {
        // Resize by percentage
    }
    public void resize(Integer x, Integer y) {
        // Resize by coordinates
    }
}

For parametric polymorphism, we can create parameterized types using < and >. For example, the ArrayList class has a type parameter E that specifies the type of objects the list can store. This type parameter is also used as the formal parameter of the add method. Thus, the type expected by add is the same one used when instantiating ArrayList. Note that it extends AbstractList and implements the List interface, both using the same type E, as well as other interfaces.

public class ArrayList<E> extends AbstractList<E>
       implements List<E>, RandomAccess, Cloneable,
                  java.io.Serializable {
    public boolean add(E element) {
        // Implementation of add
    }
    // Other methods and fields of the class
}

For example, we can create a list of strings with the type parameter String and insert elements by calling add1.

var names = new ArrayList<String>();
names.add("Mary");
names.add("John");

In this case, the compiler expects the argument passed to add to be a String. Passing an object of another type causes a compile-time error. If we wanted a list of integers, we could pass a different type to ArrayList, in this case Integer:

var numbers = new ArrayList<Integer>();
numbers.add(10);
numbers.add(20);

For subtype polymorphism, suppose the Figure class has a draw method that can also be called on objects of the Triangle, Circle, and Rectangle classes. Suppose that each of these classes overrides the method to implement its own drawing behavior. In the following code, each call to draw invokes a different implementation according to the class of the object assigned to figure: Circle, Triangle, or Rectangle.

Figure figure = new Circle(10);
// Calls Circle's draw method
figure.draw();
figure = new Triangle(20);
// Calls Triangle's draw method
figure.draw();
figure = new Rectangle(10, 20);
// Calls Rectangle's draw method
figure.draw();

This article is adapted from content in Orientação a Objetos com Java, a book by Eduardo Kessler Piveta.

  1. To use ArrayList, we usually need to import it with import java.util.ArrayList;. Imports are not required in compact source files. In regular source files, the java.base module can be imported with import module java.base;. ↩