java中抽象类和抽象方法的设计理念及其优点

第一部分:为什么需要抽象类和抽象方法?(理论解释)

想象一下,在面向对象编程中,我们用“类”来描述现实世界中的事物。但有些事物或概念本身是抽象的、不具体的。你无法在现实世界中找到一个叫做“动物”的东西,你能找到的都是具体的“狗”、“猫”、“鸟”。

“动物”就是一个抽象的概念,而“狗”、“猫”、“鸟”是它的具体实例。然而,“动物”这个概念是有意义的,因为我们可以总结出所有动物都具备的共同特征(比如:有生命、会吃东西)和共同行为(比如:会发出声音)。

Java中的抽象类和抽象方法,就是为了对这种“抽象概念”进行建模而设计的。

1. 抽象类的核心作用

抽象类(abstract class)就像一个不完整的模板或者蓝图。它的核心作用有两点:

  • 抽离共性,实现代码复用
    所有子类都具备的共同属性(成员变量)和共同行为(具体方法),可以直接在抽象父类中实现。这样,子类就不需要重复编写这些代码,直接继承即可。这遵循了不要重复自己(Don’t Repeat Yourself, DRY)的编程原则。

  • 定义规范,强制子类实现
    有些行为,父类知道必须要有,但具体怎么做则由每个子类自己决定。例如,我们知道所有“动物”都会“发出声音”,但狗是“汪汪”叫,猫是“喵喵”叫。这种“只定义规范,不提供实现”的行为,就用抽象方法(abstract method来表示。任何继承这个抽象类的子类,都必须实现这个抽象方法,否则它自己也必须被声明为抽象类。这相当于一种强制性的约定或合同

2. 抽象方法的角色

抽象方法(abstract method)是一个只有方法声明,没有方法体(没有 {} 代码块)的方法。它存在的唯一目的,就是在父类层面规定一个行为契约

  • 它告诉所有子类:“嘿,如果你想成为我这一族的一员,你就必须拥有这个功能,并且要自己去实现它。”
3. 这样设计的优点
  1. 模板化设计:抽象类提供了一个模板,既包含了子类通用的功能(具体方法),又为子类必须实现的特定功能预留了位置(抽象方法)。这让类的层级结构非常清晰。

  2. 强制性约束:通过抽象方法,可以确保所有子类都具备某些必要的功能,避免了子类开发者忘记实现关键方法,从而保证了体系的完整性和健壮性。

  3. 拥抱多态(Polymorphism):这是最核心的优点之一。因为抽象类保证了所有子类都实现了某个方法(比如 makeSound()),所以我们可以用父类(抽象类)的引用去指向任何一个子类的对象,并安全地调用那个方法。程序在运行时会自动调用该子类自己实现的版本。这让代码更加灵活、可扩展。

  4. 提高可维护性:如果所有子类共有的一个功能需要修改,我们只需要在抽象父类中修改一次即可,所有子类都会自动继承这个变更,大大提高了代码的可维护性。


第二部分:一个具体的例子:图形(Shape)

让我们用一个经典的“图形”例子来演示抽象类和抽象方法的威力。

场景:我们要创建一个程序来计算不同图形(如圆形、矩形)的面积。

分析

  • “图形”本身是一个抽象概念。你无法画一个“图形”,你只能画一个“圆形”或“矩形”。所以 Shape 类适合做成抽象类。
  • 所有图形都有一些共性,比如它们都有一个“名字”。这个可以做成具体方法。
  • 所有图形都能计算面积,但计算公式完全不同。圆形是 πr²,矩形是长×宽。所以,“计算面积”这个行为就适合做成抽象方法。
第1步:创建抽象父类 Shape

这个类定义了所有图形的“模板”。

// 定义一个抽象类 Shape
public abstract class Shape {
    
    // 这是一个具体属性,所有子类都将拥有它
    protected String name;

    // 构造方法,用于初始化通用属性
    public Shape(String name) {
        this.name = name;
    }

    // 这是一个具体方法,提供了通用功能的实现,子类可以直接复用
    public String getName() {
        return this.name;
    }

    // 这是一个抽象方法,它只定义了规范(必须能计算面积),但没有提供具体实现
    // 它强制所有子类必须自己实现这个方法
    public abstract double calculateArea(); 
}

解读

  • Shape 类被 abstract 修饰,所以你不能 new Shape()
  • 它有具体的属性 name 和具体的方法 getName(),这部分代码可以被所有子类复用
  • 它有一个抽象方法 calculateArea(),这部分是规范,强制子类必须实现。
第2步:创建具体的子类 CircleRectangle

现在,我们来创建继承 Shape 的具体图形类。

// Circle 类继承了 Shape
public class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        super("圆形"); // 调用父类的构造方法来设置名字
        this.radius = radius;
    }

    // 必须实现父类中定义的抽象方法 calculateArea()
    @Override
    public double calculateArea() {
        // 提供圆形的面积计算公式
        return Math.PI * radius * radius;
    }
}

// Rectangle 类继承了 Shape
public class Rectangle extends Shape {
    private double width;
    private double height;

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

    // 必须实现父类中定义的抽象方法 calculateArea()
    @Override
    public double calculateArea() {
        // 提供矩形的面积计算公式
        return width * height;
    }
}

解读

  • CircleRectangleextends Shape
  • 它们都必须通过 @Override 来实现 calculateArea() 方法,否则Java编译器会报错。这就是抽象类的强制约束作用。
  • 它们都可以直接使用从 Shape 继承来的 getName() 方法,这就是代码复用
第3步:演示多态的威力

现在看看使用这些类有多么方便和灵活。

public class Main {
    public static void main(String[] args) {
        // 创建具体的图形对象
        Shape circle = new Circle(10.0);
        Shape rectangle = new Rectangle(5.0, 8.0);
        
        // 使用一个 Shape 类型的数组来统一管理不同的图形
        Shape[] shapes = {circle, rectangle};
        
        // 遍历数组,调用每个图形的方法
        for (Shape shape : shapes) {
            // 调用的是父类 Shape 的具体方法 getName() - 代码复用
            System.out.println("图形名称: " + shape.getName());
            
            // 调用的是被子类重写的 calculateArea() - 多态
            // 程序在运行时,会自动判断 shape 的真实类型(是Circle还是Rectangle)
            // 然后调用对应子类中的 calculateArea() 方法
            System.out.println("图形面积: " + shape.calculateArea());
            System.out.println("--------------------");
        }
    }
}

运行结果

图形名称: 圆形
图形面积: 314.1592653589793
--------------------
图形名称: 矩形
图形面积: 40.0
--------------------

解读

  • main 方法中,我们用父类 Shape 的引用(Shape circle = ...)来指向子类的对象。
  • 最关键的是 for 循环:循环中的 shape 变量是 Shape 类型,但它在运行时可以指向 Circle 对象或 Rectangle 对象。
  • 当我们调用 shape.calculateArea() 时,Java虚拟机(JVM)会执行动态绑定
    • shape 指向 Circle 对象时,调用的是 Circle 类中的 calculateArea()
    • shape 指向 Rectangle 对象时,调用的是 Rectangle 类中的 calculateArea()
  • 这就是多态。我们不需要写 if (shape instanceof Circle) 这样的判断,代码非常简洁优雅。这一切之所以能实现,正是因为抽象类 Shape 保证了任何它的子类都必定有一个 calculateArea() 方法。

总结

设计点优点在例子中的体现
抽象类提供模板:整合了通用部分和待实现部分。Shape 类既有 name 属性和 getName() 方法,也定义了 calculateArea() 的规范。
具体方法代码复用:减少重复代码,提高可维护性。CircleRectangle 都能直接使用 getName(),无需重写。
抽象方法定义规范:强制子类实现特定功能,保证体系完整。CircleRectangle 都必须实现 calculateArea(),否则编译不通过。
整体设计实现多态:允许用统一的方式处理不同的子类对象,代码灵活、可扩展。main 方法中可以用 Shape 数组统一处理 CircleRectangle,并调用各自的面积计算方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值