Polymorphism

  • Polymorphism is composed of two words - “poly” which means “many”, and “morph” which means “shapes”. Therefore Polymorphism refers to something that has many shapes.
  • Polymorphism refers to the process by which some code, data, method, or object behaves differently under different circumstances or contexts.
  • Explore:
    • virtual functions
    • pointers
  • Types:
    • Compile time Polymorphism
    • Runtime Polymorphism

Compile Time Polymorphism (aka Static or Early binding)

  • It refers to the type of Polymorphism that happens at compile time. What it means is that the compiler decides what shape or value has to be taken by the entity in the picture.

Examples

  • Method Overloading
class A
{
   void foo(int x);
   void foo(int x) const;
   void foo(int x, int y);
};
  • Function Overloading
int sum(int x, int y)
int sum(int x, int y, int z)
  • Operator Overloading
    • Supported by C++, C#
    • Not Supported by Java, Javascript
int res = 1 + 2;
float f = 1.7 + 2.8;

Function vs Method

  • Method is usually used to refer a member function for class while Function is a freestanding non member function

Runtime Polymorphism (aka Dynamic or Late binding)

  • Refers to the type of Polymorphism that happens at the run time.
  • What it means is it can’t be decided by the compiler.
  • Therefore what shape or value has to be taken depends upon the execution. Hence the name Runtime Polymorphism.
  • Dynamic Dispatch is also similar to Virtual Method Invocation
  • See final vs virtual
  • Examples:
    • Method Overriding (which uses Dynamic Dispatch) : call to an overridden method is resolved at runtime rather than compile-time

We can achieve it by two methods:

  • Through inheritance
class Animal {
    public void walk() {
        System.out.println("Animal is walking");
    }
}
 
class Cat extends Animal {
    @Override
    public void walk() {
        System.out.println("Cat is walking");
    }
}
 
public static void main() {
    Animal a = new Cat();
    a.walk(); // Cat is walking
}
  • Through interface
interface IMessage {
    public String getMessage();
}
 
class XMLMessage implements IMessage {
    @Override
    public String getMessage() {
        return "This is an XML Message";
    }
}
 
class SOAPMessage implements IMessage {
    @Override
    public String getMessage() {
        return "This is a SOAP Message";
    }
}
 
class MessagePrinter {
    public static void printMessage(IMessage message) {
        System.out.println(message.getMessage());
    }
}
 
public static void main() {
    IMessage message = new XMLMessage();
    MessagePrinter.printMessage(message); // This is an XML Message
}

Static vs Dynamic Binding

PECS

Using Generics

  • Covariance
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
 
List<? extends Number> nums = arrayList;
 
// safe to do
Number num = nums.get(0);
 
// compile error
// we cannot store anything here, because compiler cannot determine
// whether it is Integer, or Float or Double?
nums.add(45L); 
  • Contravariance
ArrayList<Number> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2L);
arrayList.add(3.4f);
 
List<? super Integer> nums = arrayList;
 
// safe to do
nums.add(45);
 
// compile error
Integer num = nums.get(0);
 
// this works
// we can only guarantee that it will be object
Object num = nums.get(0);
  • Invariance
List<Integer> nums = new ArrayList<>();
 
// safe
nums.add(1);
 
// safe
Integer i = nums.get(0);

In Method Arguments

class Super {
  Object getSomething() {}
}
class Sub extends Super {
  String getSomething() {}
}
  • Contravariance
    • Sub#doSomething is contravariant because it takes a parameter of a superclass of the parameter of Super#doSomething (but, again, fulfills the contract of Super#doSomething)
    • Java does not support this!
class Super {
  void doSomething(String parameter)
}
class Sub extends Super {
  void doSomething(Object parameter)
}

Real World Examples

  • Covariance
    • Collections.max(Collection<? extends T>)
public void processSalaries(List<? extends Employee> employees) {
    for (Employee e : employees) {
        System.out.println(e.getSalary());
    }
}
  • Contravariance
    • Collections.sort(List<T>, Comparator<? super T>)
    • stream.forEach(Consumer<? super T> action)
public void dispatchMessages(List<? super Message> queue) {
    queue.add(new Message("Hello"));
    queue.add(new ErrorMessage("Something went wrong"));
    queue.add(new Message("Bye"));
}