CodeGym /课程 /JAVA 25 SELF /多态与抽象类的关系

多态与抽象类的关系

JAVA 25 SELF
第 18 级 , 课程 4
可用

1. 抽象类与抽象方法

生活中(以及编程中)有时会想:“嗯,我不清楚具体该怎么做,但我确定这件事必须存在!” 例如,所有动物都应该会发出声音,但发出什么声音取决于具体的动物。为此,Java 引入了抽象类抽象方法

抽象类是不能被直接实例化的类(如果 Animal 是抽象的,就不能写 new Animal()),但它可以被继承。这样的类既可以包含普通(已实现)的实例方法,也可以包含抽象方法——即只声明而未实现的方法。

抽象方法没有方法体。它通过关键字 abstract 声明,并且必须由子类实现(除非该子类本身也是抽象类)。

生活中的例子

假设我们有一个动物园应用。我们希望所有动物都有方法 makeSound(),但并不知道它们各自发出的具体声音。那么我们就定义一个抽象类:

public abstract class Animal {
    public abstract void makeSound(); // 抽象方法
}

具体的动物按各自的方式实现该方法:

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

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

此时,如果有人尝试创建 new Animal(),编译器会立刻说:“抱歉,抽象的动物在自然界中并不存在!” 这很有用:你可以保证程序中只会存在具有具体行为的具体动物。

2. 通过抽象实现多态

抽象是为一组对象提取共同的对外约定(契约)。抽象类正是用来定义这种共同契约:它规定所有子类必须实现哪些方法。

多态与抽象是搭档:抽象类保证所有子类都拥有所需的方法,而多态允许你通过基类引用来调用这些方法。

示例:搭建一个小型动物园

我们有抽象类 Animal 及其多个子类:

public abstract class Animal {
    public abstract void makeSound();
}
public class Cow extends Animal {
    @Override
    public void makeSound() {
        System.out.println("哞——!");
    }
}
public class Duck extends Animal {
    @Override
    public void makeSound() {
        System.out.println("嘎嘎!");
    }
}

现在我们可以创建一个动物数组:

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

for (Animal animal : zoo) {
    animal.makeSound(); // 会为每个动物调用“正确的”方法
}

数组中的每个对象都是具体的动物,但在代码中它们的静态类型都是 Animal。借助抽象与多态,我们可以确信每个对象都拥有 makeSound() 方法,并且会得到正确的行为。

3. 使用抽象类来实现多态

再看一个更贴近实际的例子。设想我们在开发一个公司员工管理应用。员工有不同的类型:经理、开发、测试。大家都有一个共同的方法 work(),但实现方式各不相同。

抽象类 Employee

public abstract class Employee {
    protected String name;

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

    public abstract void work();
}

具体子类

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

    @Override
    public void work() {
        System.out.println(name + " 领导团队。");
    }
}

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

    @Override
    public void work() {
        System.out.println(name + " 编写代码。");
    }
}

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

    @Override
    public void work() {
        System.out.println(name + " 测试应用程序。");
    }
}

多态的使用

现在我们可以创建一个员工数组,并对每个员工调用 work() 方法:

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

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

输出结果:

Anna 领导团队。
Ivan 编写代码。
Maria 测试应用程序。

请注意:在循环中我们既不知道(也不想知道!)员工的具体类型。我们只调用 work(),每个对象都会完成自己的工作。

4. 一些有用的细节

实现保证
抽象类会强制所有子类实现所需的方法。如果你忘了在子类中实现某个抽象方法,编译器会立刻提醒你:“你必须实现它!”

通用接口
处理抽象类型的数组或列表(如 Employee[]List<Animal>)的代码可以做到高度通用。你可以添加新的子类——而无需修改主流程代码。

防止“来路不明”的对象
由于抽象类不能被直接实例化,没人会误创建一个“不明所以”的类型对象,而该对象又没有实现所需的方法。

理论与语法:如何声明抽象类与方法

  • 抽象类:在 class 之前加上关键字 abstract
  • 抽象方法:使用关键字 abstract 声明且没有方法体(只有分号)。
  • 只要类中至少有一个抽象方法,该类就必须声明为抽象类。
  • 继承抽象类的类必须实现其所有抽象方法,否则该类本身也必须是抽象的。

示意

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

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

5. 使用抽象类的常见错误

错误 1:尝试实例化抽象类。
new Animal() 这样的代码无法编译。抽象类就像没有配件的家具装配说明书:在出现具体子类之前,你无法把对象“组装”出来。

错误 2:在子类中忘记实现抽象方法。
如果你声明了抽象方法,却没有在子类中实现它(也没有把子类声明为抽象类),编译器会报错。

错误 3:忽略访问修饰符的约束。
重写方法的访问级别不能比基类更严格。比如,如果抽象方法是 public,那么实现也必须是 public(而不是 protectedprivate)。

错误 4:把抽象方法写成了有方法体的样子。
abstract 方法不能有方法体,否则编译器会翻白眼并说:“请先想清楚——要么是抽象,要么是实现!”

错误 5:把多态指望在静态方法上。
多态只对实例方法有效。静态方法不会被重写——它们是隐藏的,因此调用时的行为取决于变量的静态类型,而不是实际对象。

1
调查/小测验
多态和重载第 18 级,课程 4
不可用
多态和重载
多态和重载
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION