Welcome to Abstraction

Welcome back! Previously, you delved into polymorphism and learned how to create more flexible code structures using classes and inheritance. In this session, we will take a step further and explore a crucial aspect of Object-Oriented Programming: Abstraction.

Understanding Abstract Classes and Abstract Methods

Abstract classes and abstract methods are essential tools for achieving abstraction. They allow you to define a common interface for a group of derived classes, ensuring that specific methods are implemented. This approach helps you write more robust and scalable programs.

1. Defining an Abstract Class

Let's revisit some of the key concepts through the following code example:

// Define an abstract class Shape. Note that the abstractness is achieved by having at least one abstract method.
abstract class Shape {
    // Field variables
    private String color;

    // Constructor to initialize color
    public Shape(String color) {
        this.color = color;
    }

    // Abstract methods for calculating the area and perimeter
    abstract double area();
    abstract double perimeter();

    // Concrete method to get the color
    public String getColor() {
        return color;
    }
}

In this snippet, we define an abstract class Shape with a field variable color, a constructor to initialize the color, and a concrete method getColor to retrieve the color. The class also contains two abstract methods: area and perimeter. An abstract class can have field variables and fully defined methods, but it must contain at least one abstract method, making it impossible to instantiate directly.

2. Implementing the Abstract Methods in Derived Classes

Next, we create concrete classes that extend the abstract class Shape:

Circle Class
// Define a Circle class that inherits from Shape
class Circle extends Shape {
    private double radius;

    // Constructor to initialize the radius and color
    Circle(double radius, String color) {
        super(color);
        this.radius = radius;
    }

    // Implement the area and perimeter methods
    @Override
    double area() {
        return Math.PI * radius * radius;
    }

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

Here, the Circle class inherits from Shape and provides concrete implementations for the abstract methods area and perimeter. It also includes a constructor to initialize the radius and color by calling the super constructor from the Shape class.

Rectangle Class
// Define a Rectangle class that inherits from Shape
class Rectangle extends Shape {
    private double width, height;

    // Constructor to initialize the width, height, and color
    Rectangle(double width, double height, String color) {
        super(color);
        this.width = width;
        this.height = height;
    }

    // Implement the area and perimeter methods
    @Override
    double area() {
        return width * height;
    }

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

Similarly, the Rectangle class inherits from Shape and implements the necessary methods area and perimeter. It also includes a constructor to initialize the width, height, and color, ensuring that all necessary attributes are properly initialized.

3. Using the Abstract Class and Derived Classes

Finally, let's see how we can use these classes in a main method:

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

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

        // Using a Shape reference to a Circle object
        Shape shape = new Circle(3, "Green");
        System.out.println("Shape Area: " + shape.area() + ", Perimeter: " + shape.perimeter() + ", Color: " + shape.getColor());

        // Uncommenting the following line will cause an error as you can't instantiate an abstract class
        // Shape invalidShape = new Shape("Yellow");
    }
}

By running the main method, you can see how the Circle and Rectangle classes correctly implement the area and perimeter methods defined in the Shape abstract class, while also utilizing the getColor method. The example also demonstrates polymorphism by using a Shape reference to a Circle object.

It's important to note that attempting to instantiate an abstract class directly, as shown by the commented-out line, will result in a compilation error. This reinforces the concept that abstract classes are meant to be subclassed, not instantiated.

Why It Matters

Abstract classes and abstract methods offer a way to enforce certain patterns and rules in your code. They allow you to design a system where different types of objects can be treated uniformly while ensuring that specific behaviors are implemented in each derived class.

By mastering abstract classes and abstract methods, you'll be able to:

  1. Create more organized and readable code: You'll have a clear structure that dictates how certain methods should behave.
  2. Encourage code reusability: Common code can reside in abstract base classes, reducing redundancy.
  3. Enhance flexibility: Easily add new types of derived classes without modifying existing code — a key principle of software design.

Intrigued? Let's move on to the practice section and solidify these concepts together. You're on your way to becoming proficient in building sophisticated and maintainable systems!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal