Java > Object-Oriented Programming (OOP) > Polymorphism > Abstract Classes and Interfaces

Polymorphism with Abstract Classes and Interfaces: Shape Example

This example demonstrates polymorphism using abstract classes and interfaces in Java. It shows how different shapes (Circle, Rectangle) can implement a common interface (Shape) or extend an abstract class (AbstractShape) and be treated uniformly through a common type.

The Shape Interface

The Shape interface defines the contract that all shapes must adhere to. It declares two methods: area() and perimeter(). Any class implementing this interface must provide implementations for these methods.

public interface Shape {
    double area();
    double perimeter();
}

The Circle Class

The Circle class implements the Shape interface. It provides concrete implementations for the area() and perimeter() methods specific to a circle, using its radius. The @Override annotation indicates that these methods are implementing methods from the interface.

public class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public double perimeter() {
        return 2 * Math.PI * radius;
    }

    public double getRadius() {
        return radius;
    }
}

The Rectangle Class

The Rectangle class also implements the Shape interface, providing its own specific implementations for area() and perimeter() based on its length and width.

public class Rectangle implements Shape {
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double area() {
        return length * width;
    }

    @Override
    public double perimeter() {
        return 2 * (length + width);
    }

    public double getLength() {
        return length;
    }

    public double getWidth() {
        return width;
    }
}

Demonstrating Polymorphism

This Main class demonstrates polymorphism. We create instances of Circle and Rectangle and assign them to variables of type Shape. We can then call the area() and perimeter() methods on these variables, and the correct implementation will be executed based on the actual type of the object at runtime. The loop iterates through an array of Shape objects, and the correct area() and perimeter() methods are called for each shape, regardless of whether it's a circle or a rectangle. This is the essence of polymorphism.

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

        System.out.println("Circle Area: " + circle.area());
        System.out.println("Circle Perimeter: " + circle.perimeter());
        System.out.println("Rectangle Area: " + rectangle.area());
        System.out.println("Rectangle Perimeter: " + rectangle.perimeter());

        Shape[] shapes = new Shape[2];
        shapes[0] = circle;
        shapes[1] = rectangle;

        for (Shape shape : shapes) {
            System.out.println("Area: " + shape.area());
            System.out.println("Perimeter: " + shape.perimeter());
        }
    }
}

Abstract Shape Class

An abstract class, AbstractShape, is defined with a name and abstract methods for area() and perimeter(). Abstract classes cannot be instantiated directly but can be subclassed.

public abstract class AbstractShape {
    private String name;

    public AbstractShape(String name) {
        this.name = name;
    }

    public abstract double area();

    public abstract double perimeter();

    public String getName() {
        return name;
    }
}

Circle Extending AbstractShape

The CircleExtendsAbstract class extends AbstractShape and provides concrete implementations for the area() and perimeter() methods, similar to the Circle class implementing the Shape interface.

public class CircleExtendsAbstract extends AbstractShape {
    private double radius;

    public CircleExtendsAbstract(String name, double radius) {
        super(name);
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public double perimeter() {
        return 2 * Math.PI * radius;
    }

    public double getRadius() {
        return radius;
    }
}

Concepts Behind the Snippet

  • Polymorphism: The ability of an object to take on many forms. In this case, both Circle and Rectangle can be treated as Shape objects.
  • Abstract Class: A class that cannot be instantiated and is meant to be subclassed. It can contain both abstract (unimplemented) and concrete (implemented) methods.
  • Interface: A contract that specifies a set of methods that a class must implement. Interfaces define a 'can-do' relationship, while abstract classes often represent an 'is-a' relationship.

Real-Life Use Case

Consider a graphics rendering engine. You might have different shapes (circles, squares, triangles) that all need to be drawn on the screen. Using polymorphism, you can treat all these shapes as a generic 'Drawable' object and call the 'draw' method on each, without needing to know the specific type of shape. The correct drawing implementation will be called based on the object's actual type.

Best Practices

  • Favor composition over inheritance when possible. Interfaces promote loose coupling and greater flexibility.
  • Use abstract classes when you want to provide a common base class with some default implementation.
  • Design your interfaces to be small and focused (Interface Segregation Principle).

Interview Tip

Be prepared to explain the difference between interfaces and abstract classes. Interfaces define a contract, while abstract classes provide a partial implementation. A class can implement multiple interfaces but can only inherit from one abstract class.

When to Use Them

  • Use interfaces when you want to define a set of methods that multiple unrelated classes should implement.
  • Use abstract classes when you have a common base class with some shared behavior and you want to enforce a certain structure for its subclasses.

Memory Footprint

Interfaces themselves do not directly contribute to memory footprint at runtime. However, classes implementing an interface will have the memory footprint associated with the methods that are implemented. Abstract classes, on the other hand, have a small overhead compared to interfaces due to the potential for instance variables and concrete methods.

Alternatives

Instead of interfaces, you can use abstract classes or concrete classes (though less flexible). Instead of abstract classes, you can use interfaces with default method implementations (Java 8 and later).

Pros

  • Polymorphism: Allows you to write more generic and reusable code.
  • Abstraction: Hides implementation details and exposes only the necessary interface.
  • Flexibility: Makes your code more adaptable to change.

Cons

  • Complexity: Can increase the complexity of your code, especially with deep inheritance hierarchies.
  • Overhead: Can introduce a small performance overhead due to dynamic method dispatch.

FAQ

  • What is the difference between an abstract class and an interface?

    An abstract class can have both abstract and concrete methods, while an interface can only have abstract methods (until Java 8, which introduced default methods in interfaces). A class can inherit from only one abstract class but can implement multiple interfaces.
  • Why use polymorphism?

    Polymorphism allows you to write code that can work with objects of different classes in a uniform way. This promotes code reusability and flexibility.
  • Can an interface extend another interface?

    Yes, an interface can extend one or more other interfaces. This allows you to create more complex interfaces by combining simpler ones.