CodeGym /Courses /JAVA 25 SELF /The relationship between polymorphism and abstract classe...

The relationship between polymorphism and abstract classes

JAVA 25 SELF
Level 18, Lesson 4
Available

1. Abstract classes and methods

Sometimes in life (and in programming) you want to say: “Well, I don’t know exactly how this is done, but I know for sure it must exist!” For example, all animals should be able to make a sound, but which one depends on the specific animal. That’s exactly what abstract classes and abstract methods in Java were designed for.

An abstract class is a class that cannot be instantiated directly (you cannot write new Animal() if Animal is abstract), but it can be extended. Such a class may contain both regular (implemented) methods and abstract ones—that is, declared but not implemented.

An abstract method is a method without a body. It is declared with the abstract keyword and must be implemented in subclasses (unless the subclass itself is abstract).

A real-world example

Suppose we have a zoo application. We want all animals to have a makeSound() method, but we don’t know exactly which sound they make. Then we create an abstract class:

public abstract class Animal {
    public abstract void makeSound(); // Abstract method
}

And concrete animals implement this method in their own way:

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof-woof!");
    }
}

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

Now, if someone tries to create new Animal(), the compiler will immediately say: “Sorry, but abstract animals don’t exist in nature!” This is useful: you guarantee that only concrete animals with concrete behavior will exist in the program.

2. Polymorphism through abstraction

Abstraction is identifying a common interface for a group of objects. An abstract class defines exactly that common interface: it states which methods must be implemented by all subclasses.

Polymorphism and abstraction work together: the abstract class guarantees the required methods exist in all descendants, and polymorphism lets you call those methods through a reference of the base type.

Example: building a zoo

Let’s assemble a small zoo. We have an abstract class Animal and several of its subclasses:

public abstract class Animal {
    public abstract void makeSound();
}
public class Cow extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Moo!");
    }
}
public class Duck extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Quack-quack!");
    }
}

Now we can create an array of animals:

Animal[] zoo = {
    new Dog(),
    new Cat(),
    new Cow(),
    new Duck()
};

for (Animal animal : zoo) {
    animal.makeSound(); // The "correct" method will be called for each animal
}

Each object in the array is a concrete animal, but for the code it’s just an Animal. Thanks to polymorphism and abstraction, we can be sure that every object has the makeSound() method, and it will work correctly.

3. Using abstract classes for polymorphism

Let’s consider a more practical example. Imagine we are building an application to manage company employees. We have different types of employees: managers, developers, testers. They all have a common work() method, but they perform it differently.

Abstract class Employee

public abstract class Employee {
    protected String name;

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

    public abstract void work();
}

Concrete subclasses

public class Manager extends Employee {
    public Manager(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(name + " manages the team.");
    }
}

public class Developer extends Employee {
    public Developer(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(name + " writes code.");
    }
}

public class Tester extends Employee {
    public Tester(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(name + " tests the application.");
    }
}

Using polymorphism

Now we can create an array of employees and call the work() method for each:

Employee[] employees = {
    new Manager("Anna"),
    new Developer("Ivan"),
    new Tester("Maria")
};

for (Employee e : employees) {
    e.work();
}

Output:

Anna manages the team.
Ivan writes code.
Maria tests the application.

Notice: we don’t know (and don’t want to know!) in the loop what the exact employee type is. We simply call work(), and each object does its job.

4. Useful nuances

Guarantee of method implementation
An abstract class forces all subclasses to implement the required methods. If you forget to implement an abstract method in a subclass, the compiler will promptly remind you: “You must do it!”

A universal interface
Code that works with an array or list of an abstract type (Employee[], List<Animal>) can be completely generic. You can add new subclasses—and you won’t have to change the main code.

Protection against rogue objects
Since an abstract class cannot be instantiated directly, no one can accidentally create an “odd” object that doesn’t implement the required methods.

Theory and syntax: how to declare an abstract class and method

  • An abstract class is declared with the abstract keyword before class.
  • An abstract method is declared with the abstract keyword and has no body (just a semicolon).
  • A class with at least one abstract method must itself be abstract.
  • A class that extends an abstract class must implement all of its abstract methods, or be abstract itself.

Diagram

public abstract class Animal {
    public abstract void makeSound();
}

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

5. Common mistakes when working with abstract classes

Error #1: attempting to instantiate an abstract class.
Code like new Animal() won’t compile. Abstract classes are like an assembly manual without the actual parts: until a concrete subclass appears, you can’t assemble an object.

Error #2: forgot to implement an abstract method in a subclass.
If you declared an abstract method but didn’t implement it in the subclass (and didn’t make the class abstract), the compiler will complain and show an error.

Error #3: forgot about access modifiers.
An overridden method cannot have a more restrictive access modifier than in the base class. For example, if the abstract method was public, the implementation must also be public (not protected or private).

Error #4: trying to declare an abstract method with a body.
abstract methods cannot have a body—otherwise the compiler will roll its eyes and say: “Make up your mind—either abstraction or implementation!”

Error #5: polymorphism doesn’t work for static methods.
Polymorphism applies only to instance methods. Static methods are not overridden—they are hidden, so the behavior when called depends on the variable’s type, not the actual object.

1
Task
JAVA 25 SELF, level 18, lesson 4
Locked
Graphic Editor Foundation: Declaring an Abstract Class and Method
Graphic Editor Foundation: Declaring an Abstract Class and Method
1
Task
JAVA 25 SELF, level 18, lesson 4
Locked
Bringing Pets to Life: Inheritance and Implementing an Abstract Method
Bringing Pets to Life: Inheritance and Implementing an Abstract Method
1
Task
JAVA 25 SELF, level 18, lesson 4
Locked
Unified Team: Polymorphism with Abstract Classes
Unified Team: Polymorphism with Abstract Classes
1
Task
JAVA 25 SELF, level 18, lesson 4
Locked
Dynamic Farm: Abstract Classes, Fields, and Polymorphism
Dynamic Farm: Abstract Classes, Fields, and Polymorphism
1
Survey/quiz
Polymorphism and overloading, level 18, lesson 4
Unavailable
Polymorphism and overloading
Polymorphism and overloading
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION