抽象和继承的区别
继承是一个纵向的层级。
抽象是横向的层级。
继承是为了能复用代码,建立类之间的层次关系。
抽象是隐藏具体的实现细节,只暴露行为的设计思想。
抽象和继承的区别:纵向层级 vs 横向契约
在 Java 面向对象编程中,继承(Inheritance) 和 抽象(Abstraction) 是两个核心机制。它们经常被同时提及,但解决的是不同维度的问题。
简单来说:
- 继承是“纵向”的 is-a 关系 —— 解决“谁是谁”的问题
- 抽象是“横向”的 can-do 规范 —— 解决“具备什么能力”的问题
一、继承:纵向的 is-a 关系
核心思想
继承是一种代码复用机制,建立类之间的层次关系,表示“是一个(is-a)”的关系。子类从父类那里获得属性和方法,可以在不重复编写代码的前提下进行扩展或重写。
典型场景
- 猫是一个动物
- 支付宝是一种支付方式
- 人民币是一种货币
这些都可以用继承来建模。
class Pay {
String name;
public void introduction() {
System.out.println("我是" + name);
}
public void deductMoney() {
System.out.println("扣减余额");
}
}
class Alipay extends Pay {
@Override
public void deductMoney() {
System.out.println("调用支付宝接口扣减余额");
}
}
class WeChatPay extends Pay {
@Override
public void deductMoney() {
System.out.println("调用微信接口扣减余额");
}
}
public class Main {
public static void main(String[] args) {
Pay pay = new Alipay();
// 子类可以直接使用父类的属性和方法
pay.name = "支付宝";
pay.introduction();
// 如果子类重写了父类的方法,则调用的就是子类自己的方法
pay.deductMoney();
}
}输出:
我是支付宝 调用支付宝接口扣减余额
Alipay 和 WeChatPay 都属于 Pay 支付工具,都可以用来做“扣减余额”这一操作。通过继承,子类复用了父类的通用行为(如 introduction),并可以根据需要重写特定方法(如 deductMoney),实现多态。
二、抽象:定义行为规范,隐藏实现细节
核心思想
抽象是一种设计思想:它不关心“怎么做”,只关心“能不能做”。
它的目的是将共通的行为从具体实现中剥离出来,形成一个统一的“能力契约”。调用方只需要依赖这个契约,而无需知道背后的实现细节。
这正是面向对象中 “针对接口编程,而不是针对实现编程” 的体现。
抽象关注的核心是:“能做什么”(can-do)
比如:
- 所有支付方式都“能扣款”、“能退款”
- 所有交通工具都“能启动”、“能停止”
- 所有用户都“能登录”、“能查看信息”
这些“能力”应该被抽象出来,作为所有实现类必须遵守的协议。
如何实现抽象?
在 Java 中,抽象可以通过两种方式实现:
- 抽象类(
abstract class) - 接口(
interface)
两者都可以定义抽象方法(没有方法体的方法),强制子类去实现。
示例:使用抽象类定义支付行为
// 抽象类:定义支付工具必须具备的能力
abstract class Pay {
String name;
// 通用方法(有默认实现)
public void introduction() {
System.out.println("我是" + name);
}
// 抽象方法:必须由子类实现
public abstract void deductMoney(); // 扣款
public abstract void refund(); // 退款
}这个 Pay 类本身不能被实例化,但它规定了:任何继承它的子类,都必须实现 deductMoney() 和 refund() 方法。
具体实现类
class Alipay extends Pay {
@Override
public void deductMoney() {
System.out.println("调用支付宝接口完成扣款");
}
@Override
public void refund() {
System.out.println("发起支付宝原路退款");
}
}
class WeChatPay extends Pay {
@Override
public void deductMoney() {
System.out.println("调用微信支付接口扣款");
}
@Override
public void refund() {
System.out.println("通过微信支付系统退款");
}
}使用示例
public class Main {
public static void main(String[] args) {
Pay pay = new WeChatPay(); // 多态:父类引用指向子类对象
pay.name = "微信支付";
pay.introduction(); // 通用行为
pay.deductMoney(); // 具体实现
pay.refund(); // 具体实现
}
}输出:
我是微信支付 调用微信支付接口扣款 通过微信支付系统退款
抽象的本质:横向的能力约束
我们可以这样理解:
| 维度 | 说明 |
|---|---|
| 方向 | 横向层级(不是父子关系,而是能力对齐) |
| 关注点 | “这个对象有没有某种能力?” |
| 设计目标 | 解耦、可扩展、可替换 |
| 典型模式 | 策略模式、工厂模式、模板方法 |
想象一下插头和插座的关系:
- 不同国家的插头形状不同(实现不同)
- 但只要符合“三孔标准”(抽象接口),就能插入同一个插座
- 插座不关心你是国产还是进口,只关心你“能不能插上”
这就是抽象的力量:让不同的实现,遵循相同的规则,从而可以被统一调用。
抽象 vs 实现:解耦的关键
没有抽象时,代码是这样的:
// 紧耦合:直接依赖具体实现
Alipay alipay = new Alipay();
alipay.deductMoney();有了抽象后,代码变成:
// 松耦合:依赖抽象
Pay pay = getPayInstance(); // 可能是支付宝、微信、银联
pay.deductMoney(); // 运行时决定调用哪个实现这才是企业级开发中推崇的 高内聚、低耦合 设计。
三、总结对比
| 特性 | 继承(Inheritance) | 抽象(Abstraction) |
|---|---|---|
| 关系类型 | is-a(是什么) | can-do(能做什么) |
| 层级方向 | 纵向(上下级) | 横向(能力对齐) |
| 主要目的 | 代码复用、结构扩展 | 行为规范、解耦实现 |
| 实现方式 | extends 类 | abstract class 或 interface |
| 是否强制实现 | 否(可直接使用父类方法) | 是(抽象方法必须实现) |
| 设计重点 | 类的层级与共享 | 接口的统一与隔离 |
一句话总结:
继承告诉我们“你是谁”,抽象告诉我们“你能干什么”。 优秀的系统设计,往往是“用抽象定义能力,用继承实现差异”。
