源本科技 | 码上会

Java 中的抽象

2025/12/27
39
0

学习目标

  • 理解抽象的核心概念:隐藏实现,暴露接口

  • 掌握通过 抽象类接口 实现抽象的两种方式

  • 能够在实际项目中合理使用抽象提升代码可维护性与扩展性

  • 避免抽象使用中的常见误区


什么是抽象

抽象(Abstraction) 是面向对象编程(OOP)的四大核心原则之一。它的核心思想是:

“关注做什么,而不是怎么做。”

现实生活中的例子:电视遥控器

你只需按“开机”按钮,电视就打开了——
你不需要知道:

  • 电路如何通电

  • 屏幕如何点亮

  • 信号如何解码

这就是抽象:隐藏复杂内部细节,只暴露简单、必要的操作。


如何实现抽象

Java 提供两种机制来实现抽象:

方式

抽象程度

特点

抽象类(Abstract Class)

部分抽象

可包含抽象方法 + 具体方法 + 字段 + 构造器

接口(Interface)

完全抽象(100%)

默认所有方法都是 public abstract(Java 8+ 支持 default/static 方法)


抽象类

定义与规则

  • 使用 abstract 关键字声明

  • 不能被实例化(不能 new AbstractClass()

  • 可包含:

    • 抽象方法(无方法体)

    • 具体方法(有实现)

    • 成员变量

    • 构造器(用于子类初始化)

示例:图形系统

// 抽象基类 Shape
abstract class Shape {
    String color;

    // 抽象方法:子类必须实现
    abstract double area();
    public abstract String toString();

    // 具体方法:提供通用功能
    public String getColor() {
        return color;
    }

    // 构造器:子类可通过 super() 调用
    public Shape(String color) {
        System.out.println("Shape 构造器被调用");
        this.color = color;
    }
}

// 具体子类:Circle
class Circle extends Shape {
    double radius;

    public Circle(String color, double radius) {
        super(color); // 调用父类构造器
        System.out.println("Circle 构造器被调用");
        this.radius = radius;
    }

    @Override
    double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public String toString() {
        return "圆形,颜色:" + super.getColor() + ",面积:" + area();
    }
}

// 具体子类:Rectangle
class Rectangle extends Shape {
    double length, width;

    public Rectangle(String color, double length, double width) {
        super(color);
        System.out.println("Rectangle 构造器被调用");
        this.length = length;
        this.width = width;
    }

    @Override
    double area() {
        return length * width;
    }

    @Override
    public String toString() {
        return "矩形,颜色:" + super.getColor() + ",面积:" + area();
    }
}

// 测试类
public class Test {
    public static void main(String[] args) {
        Shape s1 = new Circle("红色", 2.2);
        Shape s2 = new Rectangle("黄色", 2, 4);

        System.out.println(s1.toString());
        System.out.println(s2.toString());
    }
}

输出结果:

Shape 构造器被调用
Circle 构造器被调用
Shape 构造器被调用
Rectangle 构造器被调用
圆形,颜色:红色,面积:15.205308443374602
矩形,颜色:黄色,面积:8.0

关键点

  • 父类引用指向子类对象(Shape s1 = new Circle(...)

  • 运行时动态绑定到具体实现(多态)


接口

完全抽象

定义与规则

  • 使用 interface 关键字

  • 所有方法默认为 public abstract(Java 8 前)

  • Java 8+ 支持:

    • default 方法(带默认实现)

    • static 方法

  • 字段默认为 public static final(即常量)

  • 类通过 implements 实现接口

示例:使用接口实现图形计算

// 定义接口
interface Shape {
    double calculateArea(); // 抽象方法
}

// 实现类:Circle
class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

// 实现类:Rectangle
class Rectangle implements Shape {
    private double length, width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double calculateArea() {
        return length * width;
    }
}

// 主程序
public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle(5.0);
        Shape rect = new Rectangle(4.0, 6.0);

        System.out.println("圆形面积: " + circle.calculateArea());     // ≈78.54
        System.out.println("矩形面积: " + rect.calculateArea());       // 24.0
    }
}

输出:

圆形面积: 78.53981633974483
矩形面积: 24.0

优势

  • 接口支持多重继承(一个类可实现多个接口)

  • 更适合定义“能力”(如 RunnableComparable


抽象类 vs 接口

特性

抽象类

接口

继承数量

单继承

多实现

方法实现

可部分实现

Java 8 前完全抽象

字段

可有普通字段

仅常量(public static final

构造器

适用场景

共享代码 + 模板方法

定义行为契约 / 能力

经验法则

  • 如果多个类共享大量代码 → 用抽象类

  • 如果只是定义一组行为规范 → 用接口

  • 两者可结合使用(如 List 是接口,AbstractList 是抽象类)


抽象的优势

  • 简化复杂系统:用户只需关注接口,无需了解底层

  • 解耦合:实现变化不影响调用方(符合开闭原则)

  • 提升安全性:隐藏敏感实现细节

  • 便于维护与扩展:新增子类不影响现有代码


抽象的劣势

  • 过度抽象:增加不必要的层次,使代码难以理解

  • 性能开销:方法调用需动态分派(但现代 JVM 优化良好)

  • 调试困难:调用链不直观,尤其在大型系统中


常见错误

错误

正确做法

忘记实现抽象方法

子类必须实现所有抽象方法,否则也需声明为 abstract

滥用抽象

仅在确实需要“共性 + 差异性”时使用

方法签名不一致

重写时确保方法名、参数、返回类型完全匹配(协变返回除外)

用抽象类代替接口定义能力

行为契约优先考虑接口


重点总结

  • 抽象 = 隐藏实现 + 暴露接口

  • 抽象类:适合有共享代码的类族(部分抽象)

  • 接口:适合定义行为标准(完全抽象)

  • 抽象是实现多态松耦合的基础

  • 合理使用抽象能显著提升代码的可读性、可维护性、可扩展性


思考题

  1. 为什么 Java 不允许直接实例化抽象类?这在设计上有什么意义?

  2. 在什么场景下,你会选择同时使用抽象类和接口?请举例说明。

  3. Java 8 引入 default 方法后,接口是否还能保证“100% 抽象”?为什么?