Skip to content

S02-07 面向对象-抽象类

[TOC]

抽象方法

在 Java 中,抽象方法(Abstract Method) 是面向对象编程(OOP)里的一个核心概念。简单来说,抽象方法是一种只有声明、没有具体实现(没有方法体) 的方法。它存在的意义是为了定义一种“规范”或“契约”,强制要求继承它的子类去完成具体的实现

核心特征与规则@

核心特征与规则:

  • 关键字修饰:必须使用 abstract 关键字来声明。

  • 没有方法体:抽象方法以分号 ; 结尾,而不是大括号 {}

  • 宿主限制:如果一个类包含了哪怕一个抽象方法,那么这个类必须被声明为抽象类(Abstract Class)。

    注意:抽象类中可以没有抽象方法,但有抽象方法的类一定是抽象类。

  • 强制重写(Override):当一个非抽象类(具体类)继承了包含抽象方法的抽象类时,它必须提供这些抽象方法的具体实现。如果子类不实现这些方法,那么子类也必须被声明为抽象类。

    image-20260302173733003

  • 修饰符互斥:抽象方法不能被声明为以下修饰符:

    • private:因为私有方法无法被子类继承,也就无法被重写。
    • final:因为 final 方法明确禁止被子类重写。
    • static:因为静态方法属于类级别,不能被多态重写(静态方法属于早绑定,而抽象方法依赖于运行时的晚绑定)。

基本语法

基本语法与代码示例:

下面是一个典型的使用场景:定义一个动物的抽象类,让不同的具体动物去实现它们独有的叫声。

java
// 1. 特性:包含抽象方法的类必须是抽象类
abstract class Animal {

  // 2. 特性:这是一个抽象方法:只有声明,没有实现(没有大括号)
  public abstract void makeSound();

  // 3. 特性:抽象类中也可以包含带有具体实现的普通方法
  public void sleep() {
    System.out.println("Zzz... 动物在睡觉");
  }
}

// 具体的子类:狗
class Dog extends Animal {
  // 4. 特性:具体的子类必须实现父类中所有的抽象方法
  @Override
  public void makeSound() {
    System.out.println("汪汪汪!");
  }
}

// 具体的子类:猫
class Cat extends Animal {
  // 必须实现父类的抽象方法
  @Override
  public void makeSound() {
    System.out.println("喵喵喵!");
  }
}

public class Main {
  public static void main(String[] args) {
    // Animal a = new Animal(); // 5.特性:错误!抽象类不能被实例化

    Animal myDog = new Dog(); // 6. 特性:多态机制,抽象类的实现子类的实例对象可以是抽象类类型
    Animal myCat = new Cat();

    myDog.makeSound(); // 输出: 汪汪汪!
    myCat.makeSound(); // 输出: 喵喵喵!
    myDog.sleep();     // 输出: Zzz... 动物在睡觉
  }
}

设计目的

为什么需要抽象方法(设计目的)

将共有的方法抽取到父类之后,在父类中无法做具体的实现,此时就可以定义成抽象方法,延伸到子类中去具体的实现。

  1. 定义统一的规范(契约):抽象方法允许父类说:“我知道所有的子类都应该具备这个行为,但我不知道它们具体会怎么做,所以具体做法由它们自己决定。”

  2. 实现多态:通过父类引用指向子类对象,可以在运行时动态调用子类实现的具体方法,提高代码的灵活性和可扩展性。

  3. 防止代码冗余在父类中把共同的逻辑写在普通方法里,把差异化的逻辑提取成抽象方法留给子类实现。这在设计模式中被称为 模板方法模式(Template Method Pattern)

接口中的抽象方法

接口中的抽象方法:

除了抽象类,接口(Interface) 是抽象方法的另一个主要栖息地。

  • 在 Java 8 之前,接口里的所有方法默认都是并且只能是 public abstract 的(即使你省略了这两个关键字)。

  • 从 Java 8 开始,接口中允许出现带有具体实现的 default 方法和 static 方法,但普通的非默认/静态方法依然是隐式的抽象方法。

    java
    public interface Playable {
     // 隐式为 public abstract void play();
     void play();
    }

抽象类

在 Java 中,抽象类(Abstract Class) 是对具有共同特征的实体进行的一种高度概括和抽象。它和普通类很相似,但有一个最致命的区别:抽象类不能被实例化(不能直接使用 new 关键字创建对象)

作用作为其他类的“基类(父类)”,用来抽取子类共有的属性和方法,同时通过抽象方法定义子类必须遵守的规范

核心特征与规则@

核心特征与规则:

  • 关键字修饰:必须使用 abstract 关键字修饰类(例如 public abstract class MyClass)。
  • 禁止实例化:你永远不能写出 new MyAbstractClass() 这样的代码。编译器会直接报错。抽象类只能通过其具体的子类来实例化(多态)。
  • 可以包含普通方法和变量:抽象类非常自由,它可以包含普通的成员变量(字段)、普通的具体方法(有方法体),甚至静态方法(static)和常量(final)。这一点让它在代码复用上非常强大。
  • 可以有构造方法:虽然抽象类不能被直接实例化,但它可以而且通常会有构造方法。这个构造方法是为了在子类被实例化时,通过 super() 来调用,从而初始化父类中定义的公共属性
  • 与抽象方法的关系
    • 如果一个类包含了抽象方法,它必须被声明为抽象类。
    • 但是,抽象类不一定非要有抽象方法。一个没有包含任何抽象方法的类也可以被声明为抽象类,目的仅仅是不让别人直接实例化它。
  • 强制重写(Override):当一个非抽象类(具体类)继承了包含抽象方法的抽象类时,它必须提供所有这些抽象方法的具体实现。如果子类不实现这些方法,那么子类也必须被声明为抽象类。

代码示例:带状态和构造的抽象类:

与接口不同,抽象类最大的优势是可以保存状态(成员变量)。下面用一个图形(Shape)的例子来展示:

java
// 0. 定义一个抽象类:图形,使用 abstract 关键字
abstract class Shape {

    // 1. 抽象类可以有普通的成员变量
    protected String color;

    // 2. 抽象类可以有构造方法,供子类调用
    public Shape(String color) {
        this.color = color;
    }

    // 3. 抽象方法:强制子类必须提供计算面积的具体实现
    public abstract double getArea();

    // 4. 具体方法:所有子类共享的逻辑,无需重写
    public void display() {
        System.out.println("这是一个颜色为 " + color + " 的图形,面积是: " + getArea());
    }
}
java
// 具体子类:圆形
class Circle extends Shape {
    private double radius;

    public Circle(String color, double radius) {
        super(color); // 调用父类(抽象类)的构造方法初始化公有属性
        this.radius = radius;
    }

    // 必须重写父类的抽象方法
    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

// 具体子类:矩形
class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(String color, double width, double height) {
        super(color); // 调用父类构造方法
        this.width = width;
        this.height = height;
    }

    @Override
    public double getArea() {
        return width * height;
    }
}
java
// 测试类
public class Main {
    public static void main(String[] args) {
        // Shape s = new Shape("Red"); // 错误!不能实例化抽象类

        // 使用多态:父类引用指向子类对象
        Shape myCircle = new Circle("红色", 5.0);
        Shape myRect = new Rectangle("蓝色", 4.0, 6.0);

        myCircle.display(); // 输出: 这是一个颜色为 红色的图形,面积是: 78.5398...
        myRect.display();   // 输出: 这是一个颜色为 蓝色的图形,面积是: 24.0
    }
}

设计目的

为什么需要抽象类(设计目的):

  1. 代码复用(DRY 原则):把各个子类中重复的代码(包括成员变量和具体方法)抽离到抽象类中,子类只需要继承即可,避免代码冗余。

  2. 表达 "Is-A" 关系:抽象类通常代表一种强烈的所属关系。例如 Dog is a AnimalCircle is a Shape它构成了类的分类体系

  3. 模板方法设计模式:抽象类可以在父类中定义好算法的骨架(具体方法),而将一些特定的步骤延迟到子类中去实现(抽象方法)。就像上面例子中的 display() 方法,骨架已经定好,但核心的 getArea() 留给子类决定。

练习

练习题

某IT公司有多名员工,按照员工负责的工作不同,进行了部门的划分(研发部、维护部)。 研发部(Developer)根据所需研发的内容不同,又分为 JavaEE工程师 、Android工程师 ; 维护部(Maintainer)根据所需维护的内容不同,又分为 网络维护工程师(Network) 、硬件维护工程师(Hardware) 。

公司的每名员工都有他们自己的员工编号、姓名,并要做他们所负责的工作。

工作内容:

  • JavaEE工程师: 员工号为xxx的 xxx员工,正在研发电商网站
  • Android工程师:员工号为xxx的 xxx员工,正在研发电商的手机客户端软件
  • 网络维护工程师:员工号为xxx的 xxx员工,正在检查网络是否畅通
  • 硬件维护工程师:员工号为xxx的 xxx员工,正在修复电脑主板

请根据描述,完成员工体系中所有类的定义,并指定类之间的继承关系。进行XX工程师类的对象创建,完成工作方法的调用。

思路分析

image-20260303134334535

代码实现

image-20260303135450201