CodeGym /课程 /JAVA 25 SELF /继承与方法重载中的常见错误

继承与方法重载中的常见错误

JAVA 25 SELF
第 23 级 , 课程 1
可用

1. 继承中的错误

继承是 OOP 的基石之一,但也是初学者最容易踩坑的主题之一。让我们拆解典型错误,并学习如何避免。

没有调用基类构造函数 (super(...))

当你创建子类时,需要记住:基类可能要求通过构造函数进行特定初始化。如果基类没有无参构造函数,那么在子类构造函数中必须显式通过 super(...) 调用基类构造函数。

错误示例:

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

class Dog extends Animal {
    // 错误!Animal 没有无参构造函数
    public Dog() {
        // super(); // 编译器会自动插入 super(),但基类并不存在这样的构造函数!
    }
}

如何修复:

class Dog extends Animal {
    public Dog(String name) {
        super(name); // 一切正常!
    }
}

说明:
如果基类只有带参构造函数,编译器不会自动添加无参构造函数。这是编译错误的常见原因。

试图继承 final 类或重写 final 方法

在 Java 中可以将类或方法声明为 final。这意味着:

  • 该类不能被继承。
  • 该方法不能在子类中被重写(override)。

错误示例:

final class Cat {}

// 编译错误!
class Tiger extends Cat { 
    // ...
}
class Animal {
    public final void sleep() {
        System.out.println("Zzz...");
    }
}

class Dog extends Animal {
    // 编译错误!
    @Override
    public void sleep() {
        System.out.println("Dog is sleeping...");
    }
}

说明:
如果你看到错误 "cannot inherit from final""cannot override final method" —— 请检查修饰符!

违反 Liskov 替换原则 (Liskov Substitution Principle)

听起来吓人,但实际上意味着:子类对象必须能像基类对象那样行为,不破坏程序逻辑。常见错误是以与基类期望不一致的方式重写方法。

示例:

class Bird {
    public void fly() {
        System.out.println("我在飞!");
    }
}

class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("企鹅不会飞!");
    }
}

问题是什么?
针对 Bird 编写的代码期望任何鸟都会飞。但如果传入 Penguin,程序可能会崩溃。

更好的做法:
在这种情况下应重审继承层次,或改用接口/组合。

2. 方法重载(overloading)中的错误

重载是指在同一个类中有多个同名但参数不同的方法。看似简单,但也容易踩坑。

把重写写成了重载(签名出错)

新手常常想重写(override)父类方法,却不小心改了参数。结果变成了重载,而不是重写——多态不起作用!

错误示例:

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

class Dog extends Animal {
    // 原本想重写,却变成了重载!
    public void makeSound(String extra) {
        System.out.println("Bark! " + extra);
    }
}

问题:
调用 dog.makeSound() 将调用父类的方法,而不是你新增的那个。
调用 dog.makeSound("loudly") 会调用重载版本,但多态不起作用!

最佳实践:
请使用注解 @Override —— 如果你的签名错了,编译器会立刻提示。

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

重载的不显而易见行为(自动类型转换、调用歧义)

当参数可同时匹配多个重载时,Java 可能选择与你期望不同的方法。

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

问题:
如果调用 demo.print(5L),Java 会选择 print(double x)(因为 long 比起转为 int,更倾向被转换为 double)。
如果同时存在参数类型为 ObjectIntegerint 的重载,用 null 调用可能会导致编译错误:“reference to print is ambiguous”。

仅以返回类型区分同名方法(编译错误)

在 Java 中,不能声明两个仅返回类型不同而参数列表相同的同名方法!

public class Demo {
    // 编译错误!
    public int foo() { return 1; }
    public String foo() { return "hello"; }
}

说明:
用于重载匹配的方法签名是“名称 + 参数”。返回类型不参与比较。编译器无法判断你想调用哪一个。

3. 最佳实践

为避免在继承和重载上踩坑,请遵循以下建议:

对需要重写的方法始终使用 @Override 注解

这不仅提高可读性,还能防止签名错误。一旦你不小心改了参数或方法名,编译器会立即告知。

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

清晰地区分重载与重写

  • 重写(override):改变父类方法的行为——签名必须完全一致。
  • 重载(overload):添加同名但参数不同的新方法。

直观对比表:

重载(overloading) 重写(overriding)
位置 同一类/层级内 在子类中
方法名 相同 相同
参数 不同 相同
返回类型 可以不同 必须相同/可协变
注解 非必需 @Override 推荐

不要滥用重载

如果一个方法有太多重载,代码会变得难读且混乱。若重载过多,最好使用参数对象或 Builder 模式。

4. 示例:宠物管理系统

假设你要做一个简单的宠物管理系统。你有一个基类 Pet,以及子类 CatDog

public class Pet {
    private String name;
    public Pet(String name) {
        this.name = name;
    }
    public void speak() {
        System.out.println(name + " 发出不明的声音。");
    }
}

public class Cat extends Pet {
    public Cat(String name) {
        super(name);
    }
    @Override
    public void speak() {
        System.out.println(getName() + " 说:喵!");
    }
    // 错误:Pet 中没有 getName() 方法!
}

典型错误:
在尝试重写方法时,我们调用了基类中不存在的方法。应当添加一个 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 + " 发出不明的声音。"); }
}

现在一切正常,我们可以使用多态:

Pet myPet = new Cat("Barsik");
myPet.speak(); // Barsik 说:喵!

5. 继承与重载的常见错误

错误 1:忘记调用 super(...)
如果基类构造函数中有重要逻辑而你没有调用,程序可能出现意外行为,甚至无法通过编译。

错误 2:重写错了方法。
本想修改父类方法的行为,结果却添加了一个名称相近或参数不同的新方法。结果:旧方法照旧被调用,而你的新方法没人用。

错误 3:尝试重写 final 方法。
Java 不允许这么做,这正是它的用处!若看到编译错误——请检查是否有 final

错误 4:把方法重载到难以辨认。
当你有 10 个 calculate 重载,连自己都分不清调用的是哪一个时——该停下来考虑重构了。

错误 5:违反 Liskov 替换原则。
如果子类改变了基类行为的语义,整个架构都可能“跑偏”。例如,若你有类 Shape 及其方法 getArea(),而子类 BrokenShape 返回 -1,这可能引发奇怪的 bug。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION