Polymorphism in Java

Thanoshan MV
5 min readMay 1, 2020

Polymorphism is the ability of an object to take on many forms. That is, at compile time, Java object will be in reference type but at runtime, it will be in runtime type (object type).

Polymorphism in OOP occurs when a super type references a sub type object.

All Java objects are considered to be polymorphic as they share more than one IS-A relationship (at least all objects will pass the IS-A test for their own type and for the class Object).

We can access an object through a reference variable. A reference variable can be of only one type. Once declared, the type of a reference variable cannot be changed.

A reference variable can be declared as a class or interface type.

A single object can be referred to by reference variables of many different types as long as they are the same type or a super type of the object.

Method overloading

If a class has multiple methods having same name but different in parameters, it is known as method overloading.

Method overloading rules:

  1. Must have a different parameter list.
  2. May have different return types.
  3. May have different access modifiers.
  4. May throw different exceptions.
class JavaProgrammer{
public void code() {
System.out.println("Coding in C++");
}
public void code(String language) {
System.out.println("Coding in "+language);
}
}
public class MethodOverloader {
public static void main(String[] args) {
JavaProgrammer gosling = new JavaProgrammer();
gosling.code();
gosling.code("Java");
}
}
/*
Output:
Coding in C++
Coding in Java
*/

NOTE: Static methods can also be overloaded.

public class Addition {
public static int add(int a,int b) {
return a+b;
}
public static int add(int a,int b,int c) {
return a+b+c;
}
}
public class PolyTest {
public static void main(String[] args) {
System.out.println(Addition.add(5, 5));
System.out.println(Addition.add(2, 4, 6));
}
}

NOTE: We can overload main() method but Java Virtual Machine calls the main() method that receives String arrays as arguments.

public class PolyTest {
public static void main() {
System.out.println("main()");
}
public static void main(String args) {
System.out.println("String args");
}
public static void main(String[] args) {
System.out.println("String[] args");
}
}
//Output: String[] args

Rules to follow for polymorphism

1. Compile time rules

  1. Compiler only knows reference type.
  2. It can only look in reference type for methods.
  3. Outputs a method signature.

2. Run time rules

  1. At runtime, JVM follows exact runtime type (object type) to find method.
  2. Must match compile time method signature to method in actual object’s class.

Method overriding

If a subclass has the same method as declared in the super class is known as method overriding.

Method overriding rules:

  1. Must have the same parameter list.
  2. Must have the same return type: although covariant return allows to change the return type of the overridden method.
  3. Must have same access modifier or less restrictive access modifier.
  4. Must not throw new or broader checked exceptions: may throw narrower checked exceptions and may throw any unchecked exception.
  5. final methods can’t be overridden.
  6. Only inherited methods may be overridden (must have IS-A relationship).

Example for method overriding:

public class Programmer {
public void code() {
System.out.println("Coding in C++");
}

}
public class JavaProgrammer extends Programmer{
public void code() {
System.out.println("Coding in Java");
}

}
public class MethodOverridder {
public static void main(String[] args) {
Programmer ben = new JavaProgrammer();
ben.code();
}
}
/*
Output:
Coding in Java
*/

NOTE: Static methods can’t be overridden because there are not inherited.

NOTE: Constructors can be overloaded but not overridden.

Object types and reference types

class Person{
void eat() {
System.out.println("Person is eating");
}
}
class Student extends Person{
void study() {
System.out.println("Student is studying");
}
}
public class InheritanceChecker {
public static void main(String[] args) {
Person alex = new Person();//New Person "is a" Person
alex.eat();
Student jane = new Student();//New Student "is a" Student
jane.eat();
jane.study();
Person mary = new Student();//New Student "is a" Person
mary.eat();
//Student chris = new Person(); //New Person isn't a Student.
}
}

In Person mary = new Student(); ,

This object creation is perfectly fine.

mary is Person type reference variable and new Student() will create new Student object.

mary can’t access study() in compile time because the compiler only knows the reference type and since there is no study() in reference type class, it can’t access. But in runtime mary is going to be Student type (Runtime type/ object type). Please review this post for more information on runtime type.

In this case we can convince the compiler by saying “at the runtime, mary will be Student type, so please allow me to call it”. How can we convince compiler like this? This where we use casting.

We can make mary to be Student type in compile time by casting it.

((Student)mary).study();

We’ll learn about casting next.

Object type casting

Java type casting is classified into two types:

  1. Implicit casting (widening): automatic type conversion.
  2. Explicit casting (narrowing): need explicit conversion.

In primitives, long is larger type than int . Same like in objects, parent class is larger type than child class.

Java object type casting is for reference variables.

Reference variable only refers to an object. Casting a reference variable doesn’t change the object on the heap but it labels the same object in another way by means of instance members accessibility.

For example, when a supertype references a subtype object then that reference variable is limited to access only supertype instance members. When a subtype references a subtype object then that reference variable can access all instance members from both the subtype and supertype.

I. Implicit casting

Implicit casting occurs when we are converting/ assigning a subtype to a supertype.

Superclass ref1 = new Subclass();

Here, ref1 reference variable is supertype and it references subtype object. This is implicit casting. This casting happens automatically.

II. Explicit casting

Explicit casting occurs when we are converting/ assigning a supertype to a subtype.

Subclass ref2 = (Subclass) ref1;

We can not assign a supertype reference variable to a subtype reference variable. Because supertype is larger than subtype. So, we need to explicitly cast supertype to subtype when assigning it to a subtype reference variable.

We have to be careful when casting explicitly. In explicit casting, we convince compiler to compile without any error. If we convince it wrongly, we broke its trust and will get run time error: class cast exception.

In order to perform narrowing correctly, we use instanceof operator. It checks IS-A relationship.

class A {
}

class B extends A{
public void display(){
System.out.println("Class B");
}
}

public class Test {
public static void main(String[] args) {
A objA = new B();
if(objA instanceof B){
((B)objA).display();
}
}
}

As I already stated before, we must remember one important thing when creating an object using new keyword is, the reference type should be same type or super type of the object type.

Now we know the basics of OOP.

Thank you for reading and following along the OOP series.

See you in another article!

--

--