Abstract class, interface & polymorphism

Abstract Class

We have already discussed, inheritance is a mechanism by which one object can acquire all the properties and behavior of a parent object. For example, A Mobile super class. Let’s consider, a mobile should be capable of at least –

  1. Making a voice call.
  2. Send a text message.
  3. Capture photo.

You can have multiple brands who are manufacturing mobile. Let’s assume –

  • Each brand will make voice calls and send messages in a similar way.
  • But they will capture photos differently using different megapixel camera.

You might think that’s not a big deal. You already know about inheritance. You can create a super class Mobile and create methods to call and message. All three child classes – Nokia, Samsung and MI will extend that Mobile class. The only problem is, as each mobile must be capable of taking photos, how to create a method for that feature in Mobile class? The implementation will be different in all three child classes as they are going to use different megapixel cameras and different sets of image filters.

Probably create a blank method in Mobile class and override that method in all three child classes.

public class Mobile {
    public void call() {
        System.out.println("Tring.. Tring..");
    }
    
    public void message() {
        System.out.println("Good day..");
    }
    
    public void takePhoto() {
        
    }
}
public class Nokia extends Mobile {
    @Override
    public void takePhoto() {
        System.out.println("Using 5MP camera");
    }
}
public class Samsung extends Mobile {
    @Override
    public void takePhoto() {
        System.out.println("Using 18MP camera");
    }
}
public class MI extends Mobile {
    @Override
    public void takePhoto() {
        System.out.println("Using 12MP camera");
    }
}

Let’s test the app –

Mobile nokia = new Nokia();
nokia.call();
nokia.message();
nokia.takePhoto();

Mobile samsung = new Samsung();
samsung.call();
samsung.message();
samsung.takePhoto();

Mobile mi = new MI();
mi.call();
mi.message();
mi.takePhoto();

Output:

Tring.. Tring..
Good day..
Using 5MP camera

Tring.. Tring..
Good day..
Using 18MP camera

Tring.. Tring..
Good day..
Using 12MP camera

So far so good. All three child class objects are working perfectly. But what about Mobile class objects? Let’s try to create an instance of Mobile class and call those methods.

Mobile mobile = new Mobile();
mobile.call();
mobile.message();
mobile.takePhoto();

Output:

Tring.. Tring..
Good day..

Hey! Why is it not taking photos? You probably know the reason – Mobile class is not supposed to take photos. This class just doesn’t know how to do that. It’s just defining the structure of the method and all child classes are overriding this method and implementing the actual functionality.

So our approach to create a blank method in Mobile class sort of worked. The only thing is, we have to prevent others from creating an instance of Mobile class – as this class is implemented partially.

Fortunately there is a simple way to do that. We can simply mark the Mobile class as abstract.

What is an abstract class?

An abstract class means nobody will be able to make an instance of that class. If you mark a class as abstract, the compiler will stop any code from creating an instance of that type.

So, in our example, just mark the Mobile class as abstract.

public abstract class Mobile {
    public void call() {
        System.out.println("Tring.. Tring..");
    }
    
    public void message() {
        System.out.println("Good day..");
    }
    
    public void takePhoto() {
        
    }
}

Now if you write: Mobile mobile = new Mobile(), you will get compilation error: Cannot instantiate the type Mobile.

Abstract method

Think about the Mobile class. Why did we create that? The reason is –

  1. Create the call() and message() method that the child classes will use. We know completely how it will work.
  2. Create another takePhoto() method where we don’t know the implementation. We are just defining it, to ensure every child class is capable of doing that.

Think about the second point. What will happen if child class missed to override the takePhoto() method? Is there any way to enforce – child class must override a method? Also the blank method in Mobile class is not looking really good. It’s like a match box without any match stick.

The answer to the above questions is an abstract method. An abstract method means it must be overridden in child class. Also an abstract method has no body. If you mark any method as abstract, you must mark the class abstract as well. You cannot have a non-abstract class having an abstract method.

But an abstract class can or cannot have an abstract method. It is perfectly valid if an abstract class contains only concrete methods. An abstract class doesn’t mean a class with abstract method, it simply means that class cannot be instantiated. However most of the time, you will create abstract classes with abstract methods.

When a class extends an abstract class, that class must implement all abstract methods, if not (implemented some abstract method, not all) that class also must be abstract.

So let’s rewrite our mobile class as below. Child classes will remain as it is.

public abstract class Mobile {
    public void call() {
        System.out.println("Tring.. Tring..");
    }
    
    public void message() {
        System.out.println("Good day..");
    }
    
    public abstract void takePhoto();
}

Interface

Our design of the abstract Mobile class and three individual concrete classes worked just fine. Now consider, Samnung and MI will use android operating system and Nokia will use Windows operating system. Now the way call() and message() works in android is different from windows. So, we can redesign our class structure as below –

  1. We will make all the methods in Mobile class abstract.
  2. Create two child classes of Mobile class – Android and Windows. Make them abstract as well as the implementation of taking photos is unknown. In Android class implement call and message functionality based on android OS (Operating System) and in Windows class implement Windows specific call and message method.
  3. Let Nokia extend Windows class and Samsung and MI extend Android class.
public abstract class Mobile {
    public abstract void call();
    public abstract void message();
    public abstract void takePhoto();
}
public abstract class Windows extends Mobile {
    public void call() {
        System.out.println("Windows: Tring.. Tring..");
    }
    
    public void message() {
        System.out.println("Windows: Good day..");
    }
}
public abstract class Android extends Mobile {
    public void call() {
        System.out.println("Android: Tring.. Tring..");
    }
    
    public void message() {
        System.out.println("Android: Good day..");
    }
}
public class Nokia extends Windows {
    @Override
    public void takePhoto() {
        System.out.println("Using 5MP camera");
    }
}
public class Samsung extends Android {
    @Override
    public void takePhoto() {
        System.out.println("Using 18MP camera");
    }
}
public class MI extends Android {
    @Override
    public void takePhoto() {
        System.out.println("Using 12MP camera");
    }
}

This design will work perfectly. Only one change I would suggest, instead of abstract class, make the Mobile class as an interface.

What is an interface?

An interface is a 100% abstract class. It doesn’t contain any concrete method. All it contains is public abstract methods.

As all the methods in interface are by default public and abstract, we should not explicitly specify them as that would be redundant. If we do that, that won’t throw any error, that is just not necessary.

So we can rewrite our mobile class as below –

public interface Mobile {
    void call();
    void message();
    void takePhoto();
}

Also, instead of extends, we have to use implements keyword. A class extends another class, but a class implements another interface.

public abstract class Windows implements Mobile {
    ...
}
public abstract class Android implements Mobile {
    ...
}

Just like concrete class or abstract class, interfaces can also contain variables (fields). But the variables declared inside the interface are by default public, static and final.

So, we can have a field for device type inside the Mobile interface.

public interface Mobile {
    String DEVICE_TYPE = "Mobile";
}
Implement multiple interface

You might think, just like multiple inheritance is not allowed due to ambiguity, we cannot implement multiple interfaces. But that is not true.

Let’s first revisit the issue with multiple inheritance. First create two parent classes – Parent1 and Parent2.

public class Parent1 {
    public void getColor() {
        System.out.println("Red");
    }
}
public class Parent2 {
    public void getColor() {
        System.out.println("Blue");
    }
}

Now if a child class extends both Parent1 and Parent2 and calls getColor() method, which method will be called, will it be of Parent1 or Parent2. This is clearly causing an ambiguity problem. Because of which multiple inheritance is not supported in java.

Now, convert both Parent1 and Parent2 as interfaces.

public interface Parent1 {
    void getColor();
}
public interface Parent2 {
    void getColor();
}

Think of an interface as a contract. An interface just defines what a class will be able to do if that class implements the interface. Interface doesn’t provide any implementation as all the interface methods are abstract by default.

In our example, both the interfaces have getColor() method and there is no implementation. Both the interfaces are saying – “any class that will implement me will have a getColor() method. How that method will be implemented is none of my business”.

public class Child implements Parent1, Parent2 {
    @Override
    public void getColor() {
        System.out.println("Red");
    }
}

You see, there is no ambiguity issue. The Child class is just fulfilling the contract by both the interfaces using a single method.

As there is no implementation, an interface can extends multiple interfaces as well.

public interface Child extends Parent1, Parent2 {

}

There is only one catch, if only the return type is different for the same method within a different interface, you cannot implement both the interfaces. It will throw compilation error.

Polymorphism

You might think, interfaces are not great. Abstract classes still provide partial implementation, interfaces don’t give us anything. Each and every method is abstract.

Remember polymorphism we discussed earlier. Interfaces are extremely useful for polymorphism. 

Think about the Greek roots of the term polymorphism –

  • Poly = many
  • Morph = form

So polymorphism is the ability to present the same superclass or interface for differing underlying forms.

To understand this clearly, let’s re-use our Nokia, Samsung and MI classes and Mobile interface. Let’s assume you want to write a method to test the functionality of individual mobiles. This method will accept an object and will call message(), call() and takePhoto() method one by one.

public class TestMobile {
    public static void main(String[] args) {
        Nokia nokia = new Nokia();
        test(nokia);
    }
    
    public static void test(Nokia obj) {
        obj.call();
        obj.message();
        obj.takePhoto();
    }
}

This is working perfectly and giving us output –

Windows: Tring.. Tring..
Windows: Good day..
Using 5MP camera

But wait.. what about the Samsung and MI objects? Should we create another two methods like test() that will accept Samsung objects and MI objects as shown below.

public static void test(Samsung obj) {

}
	
public static void test(MI obj) {

}

This is not good. You have to write identical code inside three different methods. Instead, we can write a heterogeneous method that will accept any object of type Mobile.

public class TestMobile {
    public static void main(String[] args) {
        Nokia nokia = new Nokia();
        Samsung samsung = new Samsung();
        test(nokia);
        test(samsung);
    }
    
    public static void test(Mobile obj) {
        obj.call();
        obj.message();
        obj.takePhoto();
    }
}

Output:

Windows: Tring.. Tring..
Windows: Good day..
Using 5MP camera

Android: Tring.. Tring..
Android: Good day..
Using 18MP camera

Remember what I said about polymorphism – it is the ability to present the same superclass or interface (e.g. Mobile) for differing underlying forms (e.g. nokia and samsung object). Here we are using Mobile (interface) reference in test() method for both nokia and samsung objects. So as per the contract of Mobile interface, both the objects can call, send messages and take photos as corresponding methods are defined inside the Mobile interface. Based on the object type, at runtime, methods are invoked.

So you see, as we have defined the capabilities inside the Mobile interface, we can use this interface as a reference type to create the polymorphic method test().

Interfaces since java 8

Since java 8, it is allowed to have static and default methods inside the interface.

Static Interface Methods

We can define a static method within an interface just like defining one in a class. That static method can be invoked using the interface name.

public interface Mobile {
    static void printDeviceType() {
        System.out.println("This is a mobile");
    }
}
Mobile.printDeviceType();

Output: This is a mobile
Default interface method

As we know, all the methods in the interface are by default abstract. But, since java 8, we can have default methods inside an interface. We can declare them with the default keyword at the beginning of the method signature and provide an implementation.

There is a valid reason why java 8 introduced the default method. Consider the Mobile interface. If you add a new abstract method in that interface, all the child classes are forced to implement that method. If you have a lot of child classes, it’s a really painful exercise. Default interface methods are an efficient way to deal with this situation. Just add the default method with implementation and it is automatically available in all the child classes.

public interface Mobile {
    default void printDeviceType() {
        System.out.println("This is a mobile");
    }
}
Nokia nokia = new Nokia();
nokia.printDeviceType();

Output: This is a mobile

There is one catch though. Since Java allows classes to implement multiple interfaces, what happens when a class implements several interfaces that contain the same default methods.

To understand this, create two interfaces having a default method with the same signature.

public interface Mobile {
    default void printDeviceType() {
        System.out.println("This is a mobile");
    }
}
public interface Gadgets {
    default void printDeviceType() {
        System.out.println("This is a gadget");
    }
}

Now create one concrete class that will implement both the interfaces.

public class Nokia implements Mobile, Gadgets {

}

You will get a compilation error at the very first line: Duplicate default methods named printDeviceType with the parameters () and () are inherited from the types Gadgets and Mobile.

To solve this ambiguity, we must explicitly provide an implementation for the conflicting method.

public class Nokia implements Mobile, Gadgets {
    @Override
    public void printDeviceType() {
        Mobile.super.printDeviceType();
    }
}

That’s it for now. Hope the concept of abstract class and interface is clear now. If you have any doubt, please ask in the comment section. I will try to answer that as soon as possible. Till then, bye bye.