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
- This association of method call to the method body is known as binding
- Static binding
- aka Early binding
- uses Type(Class) information for binding
- C only supports early binding
- Dynamic binding
- aka Late binding or Virtual binding
- uses Object to resolve binding
- C++, Java supports dynamic binding
- https://en.wikipedia.org/wiki/Name_binding
- https://en.wikipedia.org/wiki/Late_binding
PECS
- Producer Extends, Consumer Super
- Mnemonic Coined by Joshua Bloch
- http://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super
- https://dzone.com/articles/covariance-and-contravariance
- When designing generic APIs, helps determine which to use:
? extends T(Covariance)? super T(Contravariance)T(Invariance)
- Use
? extends T- when the generic structure produces
Tvalues - You want to read values
- when the generic structure produces
- Use
? super T- when the generic structure consumes
Tvalues - You want to write values
- when the generic structure consumes
- Use
T- You want to both read and write values
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
- https://stackoverflow.com/questions/8481301/covariance-invariance-and-contravariance-explained-in-plain-english
- https://stackoverflow.com/questions/2501023/give-examples-of-functions-which-demonstrate-covariance-and-contravariance-in-th
- Covariance
Sub#getSomethingis covariant because it returns a subclass of the return type ofSuper#getSomething(but fulfills the contract ofSuper.getSomething())
class Super {
Object getSomething() {}
}
class Sub extends Super {
String getSomething() {}
}- Contravariance
Sub#doSomethingis contravariant because it takes a parameter of a superclass of the parameter ofSuper#doSomething(but, again, fulfills the contract ofSuper#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"));
}