Java Abstract Classes and Polymorphism: Resolving the "Class is not abstract and does not override abstract method" Error

Dec 03, 2025 · Programming · 8 views · 7.8

Keywords: Java | abstract class | polymorphism | compilation error | object-oriented design

Abstract: This article delves into the core concepts of abstract classes and polymorphism in Java programming, using a specific error case—the compilation error "Class is not abstract and does not override abstract method"—to analyze its root causes and provide solutions. It begins by explaining the definitions of abstract classes and abstract methods, and their role in object-oriented design. Then, it details the design flaws in the error code, where the abstract class Shape defines two abstract methods, drawRectangle and drawEllipse, forcing subclasses Rectangle and Ellipse to implement both, which violates the Single Responsibility Principle. The article proposes three solutions: 1. Adding missing method implementations in subclasses; 2. Declaring subclasses as abstract; 3. Refactoring the abstract class to use a single abstract method draw, leveraging polymorphism for flexible calls. Incorporating insights from Answer 2, it emphasizes the importance of method signature consistency and provides refactored code examples to demonstrate how polymorphism simplifies code structure and enhances maintainability. Finally, it summarizes best practices for abstract classes and polymorphism, helping readers avoid similar errors and improve their programming skills.

Fundamental Concepts of Abstract Classes and Polymorphism

In Java programming, an abstract class is a class that cannot be instantiated and is typically used to define a common interface and behavior for a group of related classes. Abstract classes can include abstract methods, which have declarations but no implementations and must be implemented by subclasses. This mechanism promotes code modularity and reuse, serving as a key principle in object-oriented design. Polymorphism allows the use of parent class references to point to subclass objects, enabling dynamic method calls at runtime, which enhances program flexibility and scalability.

Error Case Analysis

In the provided code, the abstract class Shape defines two abstract methods: drawRectangle and drawEllipse. This requires any non-abstract subclass inheriting from Shape to implement both methods. However, the Rectangle class only implements drawRectangle, while the Ellipse class only implements drawEllipse, leading to the compilation error: "MyTestApp.Rectangle is not abstract and does not override abstract method drawEllipse(java.awt.Graphics) in MyTestApp.Shape". This design violates the purpose of abstract classes by forcing each subclass to implement unrelated methods, increasing code complexity and maintenance costs.

Solution 1: Adding Missing Method Implementations

The simplest solution is to add the missing method implementations in the Rectangle and Ellipse classes. For example, in the Rectangle class, an empty drawEllipse method could be added, but this leads to code redundancy and logical confusion, as a rectangle class should not be responsible for drawing ellipses. While this approach eliminates compilation errors, it does not adhere to good design principles and is not recommended.

Solution 2: Declaring Subclasses as Abstract

Another approach is to declare the Rectangle and Ellipse classes as abstract, so they do not need to implement all abstract methods. However, this prevents instantiation of these classes, making it impossible to directly create rectangle or ellipse objects and thus breaking program functionality. Therefore, this method is rarely used in practice, except for specific design requirements.

Solution 3: Refactoring the Abstract Class with a Single Abstract Method

The optimal solution is to refactor the abstract class Shape by merging the two abstract methods into a generic draw method. This way, each subclass only needs to implement one method, adhering to the Single Responsibility Principle and fully leveraging polymorphism. Below is a refactored code example:

public abstract class Shape {
    private int x, y, width, height;
    private Color fillColor;
    private Color outlineColor;
    
    public Shape(int x, int y, int width, int height, Color fillColor, Color outlineColor) {
        setXY(x, y);
        setSize(width, height);
        setFillColor(fillColor);
        setOutlineColor(outlineColor);
    }
    
    // Other setter and getter methods omitted
    
    public abstract void draw(Graphics g); // Single abstract method
}

class Rectangle extends Shape {
    public Rectangle(int x, int y, int width, int height, Color fillColor, Color outlineColor) {
        super(x, y, width, height, fillColor, outlineColor);
    }
    
    @Override
    public void draw(Graphics g) {
        g.setColor(getFillColor());
        g.fillRect(x, y, width, height);
        g.setColor(getOutlineColor());
        g.drawRect(x, y, width, height);
    }
}

class Ellipse extends Shape {
    public Ellipse(int x, int y, int width, int height, Color fillColor, Color outlineColor) {
        super(x, y, width, height, fillColor, outlineColor);
    }
    
    @Override
    public void draw(Graphics g) {
        g.setColor(getFillColor());
        g.fillOval(x, y, width, height);
        g.setColor(getOutlineColor());
        g.drawOval(x, y, width, height);
    }
}

In the MyTestApp class, the paint method can be modified to use polymorphism for calling the draw method:

public void paint(Graphics g) {
    super.paint(g);
    if (rect != null)
        rect.draw(g);
    if (oval != null)
        oval.draw(g);
}

This refactoring not only resolves the compilation error but also improves code clarity and maintainability. Through polymorphism, new shape classes can be easily extended without modifying existing code.

Supplementary Insight: Method Signature Consistency and Polymorphism

As emphasized in Answer 2, to achieve polymorphism, methods in subclasses must have the same signature as the abstract methods in the parent class, including method name, parameter types, and order. In the refactored code, the draw method is consistent across all subclasses, enabling the use of Shape references to dynamically call methods of different subclasses, for example:

Shape shape1 = new Rectangle(20, 30, 23, 75, Color.GREEN, Color.BLUE);
Shape shape2 = new Ellipse(10, 10, 10, 34, Color.RED, Color.MAGENTA);
shape1.draw(g); // Calls Rectangle's draw method
shape2.draw(g); // Calls Ellipse's draw method

This design pattern enhances code flexibility by allowing runtime determination of object behavior, which is a core advantage of object-oriented programming.

Conclusion and Best Practices

Through the analysis of this case, we can summarize the following best practices: First, when designing abstract classes, ensure that abstract methods are generic and avoid forcing subclasses to implement unrelated methods. Second, leverage polymorphism to simplify code structure by using a single abstract method to promote code reuse and extensibility. Finally, always adhere to object-oriented design principles, such as the Single Responsibility Principle and Open/Closed Principle, to improve code quality and maintainability. In practical programming, when encountering the "Class is not abstract and does not override abstract method" error, prioritize refactoring the abstract class over adding redundant method implementations. This approach not only resolves compilation errors but also elevates overall code design proficiency.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.