Chapter 26: Exploring Polymorphism and the Abstract Keyword in Java

Introduction

In this post, we'll dive into two core concepts of Object-Oriented Programming (OOP) in Java: Polymorphism and the Abstract Keyword. These features provide flexibility and reusability to our code, allowing us to create robust and scalable applications.


1. Understanding Polymorphism in Java

Polymorphism allows objects to be treated as instances of their parent class rather than their actual class. This ability supports dynamic method dispatch or method overriding, enabling runtime decisions on which method to execute.

Types of Polymorphism:

  1. Compile-Time Polymorphism (Method Overloading)

    • Occurs when multiple methods share the same name but differ in parameters (type, number, or order).
    class MathOperations {
        int add(int a, int b) {
            return a + b;
        }

        double add(double a, double b) {
            return a + b;
        }
    }

    public class Main {
        public static void main(String[] args) {
            MathOperations math = new MathOperations();
            System.out.println("Integer addition: " + math.add(5, 10));   // Output: 15
            System.out.println("Double addition: " + math.add(5.5, 10.5)); // Output: 16.0
        }
    }
  1. Runtime Polymorphism (Method Overriding)

    • Achieved when a subclass overrides a method of its superclass, allowing specific implementations depending on the object instance.
    class Animal {
        void sound() {
            System.out.println("Animal sound");
        }
    }

    class Dog extends Animal {
        @Override
        void sound() {
            System.out.println("Woof");
        }
    }

    public class Main {
        public static void main(String[] args) {
            Animal myDog = new Dog(); // Polymorphism
            myDog.sound();            // Output: Woof
        }
    }

2. Abstract Keyword in Java

The abstract keyword in Java is used to declare abstract classes and abstract methods. Abstract classes allow defining methods that subclasses must implement, creating a template for subclasses.

Key Characteristics:

  • Abstract Classes cannot be instantiated directly.

  • Abstract Methods lack a body and are declared with the abstract keyword. Subclasses must implement these methods unless they are also abstract.

Example of Abstract Class and Abstract Method:

Let's create an abstract class Shape and concrete subclasses Circle and Rectangle.

abstract class Shape {
    abstract void draw(); // Abstract method

    void description() { // Concrete method
        System.out.println("This is a shape.");
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a Circle");
    }
}

class Rectangle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a Rectangle");
    }
}

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle();
        Shape rectangle = new Rectangle();

        circle.draw();          // Output: Drawing a Circle
        rectangle.draw();       // Output: Drawing a Rectangle
        circle.description();   // Output: This is a shape.
    }
}

3. Combining Abstract Classes and Polymorphism

In practice, abstract classes and polymorphism are often used together to achieve loose coupling and extendibility. This enables flexible code design where new classes can be introduced without altering the existing logic significantly.

Example: Animal Class with Polymorphism and Abstract Method

abstract class Animal {
    abstract void sound();

    void sleep() {
        System.out.println("This animal sleeps.");
    }
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("Meow");
    }
}

class Cow extends Animal {
    @Override
    void sound() {
        System.out.println("Moo");
    }
}

public class Zoo {
    public static void main(String[] args) {
        Animal cat = new Cat();
        Animal cow = new Cow();

        cat.sound();  // Output: Meow
        cow.sound();  // Output: Moo
        cat.sleep();  // Output: This animal sleeps.
    }
}

In this example, sound() is implemented differently for each animal type, showcasing polymorphism. This setup allows adding new animals by simply extending the Animal class and implementing the sound() method.


4. Benefits of Using Abstract Classes and Polymorphism Together

  • Code Reusability: Shared methods in the abstract class reduce redundant code across subclasses.

  • Scalability: New functionalities can be added through inheritance without changing existing code.

  • Flexibility: Different subclasses can provide specific implementations for abstract methods, promoting modular code.


5. Summary and Best Practices

  • Use abstract classes when a class needs to provide a common template with some methods for subclasses.

  • Apply polymorphism to enable dynamic method invocation, making code adaptable for future requirements.

  • Avoid instantiating abstract classes directly; use them as a blueprint for subclasses.


Conclusion

Understanding Polymorphism and Abstract Classes equips you with powerful tools in Java to write more structured and adaptable code. By effectively using these principles, you can make your Java applications more maintainable and flexible.