CodeGym /Courses /JAVA 25 SELF /Pitfalls with inheritance and method overloading

Pitfalls with inheritance and method overloading

JAVA 25 SELF
Level 23, Lesson 1
Available

1. Inheritance mistakes

Inheritance is one of the pillars of OOP—and also one of those topics where beginners most often hit the same rakes. Let’s go through classic mistakes and learn how to avoid them.

Missing base-class constructor call (super(...))

When you create a subclass, remember: the base class may require specific initialization via its constructor. If the base class has no no-arg (default) constructor, then in the subclass constructor you must explicitly call a base-class constructor using super(...).

Example mistake:

class Animal {
    private String name;
    public Animal(String name) {
        this.name = name;
    }
}

class Dog extends Animal {
    // Error! Animal has no no-arg constructor
    public Dog() {
        // super(); // the compiler inserts super() automatically, but there is no such constructor!
    }
}

How to fix:

class Dog extends Animal {
    public Dog(String name) {
        super(name); // All good!
    }
}

Comment:
If the base class only has constructors with parameters, the compiler won’t add a no-arg constructor automatically. This is a common cause of compilation errors.

Attempting to extend a final class or override a final method

In Java, you can mark a class or a method as final. This means:

  • The class cannot be extended.
  • The method cannot be overridden in subclasses.

Example mistake:

final class Cat {}

// Compilation error!
class Tiger extends Cat { 
    // ...
}
class Animal {
    public final void sleep() {
        System.out.println("Zzz...");
    }
}

class Dog extends Animal {
    // Compilation error!
    @Override
    public void sleep() {
        System.out.println("Dog is sleeping...");
    }
}

Comment:
If you see "cannot inherit from final", "cannot override final method"—check the modifiers!

Violating the Liskov substitution principle (Liskov Substitution Principle)

Sounds scary, but in practice it means: a subclass object must behave like a base-class object without breaking program logic. A common mistake is overriding methods so that the new class behaves differently from what the base class promises.

Example:

class Bird {
    public void fly() {
        System.out.println("I'm flying!");
    }
}

class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Penguins don't fly!");
    }
}

What’s the issue?
Code that works with Bird expects any bird to be able to fly. But if you pass a Penguin, the program may crash.

A better approach:
In such cases, reconsider the hierarchy or use interfaces/composition.

2. Mistakes with method overloading (overloading)

Overloading is when a class has several methods with the same name but different parameters. It seems simple, but there are traps here too.

Overloading instead of overriding (signature mistake)

Beginners often want to override a base-class method but accidentally change its parameters. As a result, you get overloading rather than overriding—and polymorphism doesn’t work!

Example mistake:

class Animal {
    public void makeSound() {
        System.out.println("Some sound");
    }
}

class Dog extends Animal {
    // Intended to override, but actually overloaded!
    public void makeSound(String extra) {
        System.out.println("Bark! " + extra);
    }
}

Problem:
A call to dog.makeSound() will invoke the parent method, not your new one.
A call to dog.makeSound("loudly") will call the overloaded method, but polymorphism won’t work!

Best practice:
Use the @Override annotation—if you get the signature wrong, the compiler will tell you immediately.

@Override
public void makeSound() { /* ... */ }

Non-obvious behavior with overloading (automatic type conversion, ambiguous call)

Java may sometimes “choose” a method you didn’t expect if parameters match several overloads by type.

public class OverloadDemo {
    public void print(int x) {
        System.out.println("int: " + x);
    }
    public void print(double x) {
        System.out.println("double: " + x);
    }
}

OverloadDemo demo = new OverloadDemo();
demo.print(5);     // int: 5
demo.print(5.0);   // double: 5.0
demo.print(5L);    // long -> double: double: 5.0

Problem:
If you call demo.print(5L), Java will choose print(double x) (because long is a better conversion to double than to int).
If you have methods with parameters of types Object, Integer, int, a call with null can cause a compilation error: “reference to print is ambiguous”.

Using identical method names with only different return types (compilation error)

In Java, you cannot declare two methods with the same name and parameter list that differ only by return type!

public class Demo {
    // Compilation error!
    public int foo() { return 1; }
    public String foo() { return "hello"; }
}

Explanation:
A method signature for overloading is name + parameters. The return type is not considered. The compiler cannot figure out which method you intend to call.

3. Best practices

To avoid pitfalls with inheritance and overloading, follow these recommendations:

Always use the @Override annotation for methods you intend to override

This not only improves readability but also protects against signature mistakes. If you accidentally change parameters or the method name, the compiler will let you know immediately.

@Override
public void makeSound() {
    System.out.println("Bark!");
}

Clearly distinguish overloading and overriding

  • Overriding: you change the parent method’s behavior—the signature must match.
  • Overloading: you add a new method with the same name but different parameters.

Table for clarity:

Overloading Overriding
Where Within the same class/hierarchy In a subclass
Method name Same Same
Parameters Different Same
Return type May differ Must match/be covariant
Annotation Not required @Override recommended

Don’t overuse overloading

If a method has too many overloaded variants, the code becomes unreadable and confusing. Prefer parameter objects or the Builder pattern if there are too many options.

4. Example: a pet registry system

Suppose you are building a simple pet registry system. You have a base class Pet and subclasses Cat and Dog.

public class Pet {
    private String name;
    public Pet(String name) {
        this.name = name;
    }
    public void speak() {
        System.out.println(name + " makes an indistinct sound.");
    }
}

public class Cat extends Pet {
    public Cat(String name) {
        super(name);
    }
    @Override
    public void speak() {
        System.out.println(getName() + " says: Meow!");
    }
    // Error: there is no getName() method in Pet!
}

Typical mistake:
When trying to override a method, we call a method that doesn’t exist in the base class. Better add a getter:

public class Pet {
    private String name;
    public Pet(String name) { this.name = name; }
    public String getName() { return name; }
    public void speak() { System.out.println(name + " makes an indistinct sound."); }
}

Now everything works correctly, and we can use polymorphism:

Pet myPet = new Cat("Kitty");
myPet.speak(); // Kitty says: Meow!

5. Common mistakes with inheritance and overloading

Error No. 1: forgot to call super(...).
If the base class has important logic in its constructor and you don’t call it, the program may behave unexpectedly—or won’t even compile.

Error No. 2: overrode the wrong method.
You wanted to change a parent method’s behavior, but instead added a new method with a similar name or different parameters. Result: the old method keeps working as before, while your new one is never called.

Error No. 3: tried to override a final method.
Java won’t let you do that—and that’s good! But if you see a compilation error—look for final.

Error No. 4: overloaded a method beyond recognition.
When you have 10 variants of calculate and you yourself can’t remember which one is called—it’s time to stop and think about refactoring.

Error No. 5: violated the Liskov substitution principle.
If your subclass changes the meaning of the base class’s behavior, the entire architecture can fall apart. For example, if you have a Shape class with a getArea() method, and a subclass BrokenShape returns -1, this can lead to weird bugs.

1
Task
JAVA 25 SELF, level 23, lesson 1
Locked
Fundamental immutability of the animal world 🌳
Fundamental immutability of the animal world 🌳
1
Task
JAVA 25 SELF, level 23, lesson 1
Locked
Dog Orchestra: Extending sounds, not replacing 🐶
Dog Orchestra: Extending sounds, not replacing 🐶
1
Task
JAVA 25 SELF, level 23, lesson 1
Locked
Strict name requirement for every pet 🐱
Strict name requirement for every pet 🐱
1
Task
JAVA 25 SELF, level 23, lesson 1
Locked
Intelligent number logging system 📊
Intelligent number logging system 📊
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION