跳到内容

元数据卡

  • 前置知识:第8章(上)继承、第8章(中二)方法重写
  • 预计时间:20 分钟
  • 阅读模式:概念理解 + 对比
  • 完成标志:能说清抽象类和接口的核心区别,知道何时用哪一个

你的进度

你现在有了 MessageSender——一个"消息发送器",它的 send() 方法有默认实现。但你总觉得不对:一个通用发送器根本没有意义,具体形式应该是马、鸽子或喇叭。而且如果有人继承 MessageSender 却忘了重写 send(),Bug 就潜伏了。

更麻烦的是,如果"侦察兵"也要能发消息,但它已经继承了 Soldier——Java 只有单继承。怎么办?

你的任务:让父类只定义"必须做什么",不提供默认实现。再把"能发消息"这个能力独立出来,让任何类都能拥有。


第一幕:抽象类——故事讲一半

抽象类用 abstract 关键字,把方法签名写出来,但不写方法体

java
public abstract class AbstractMessageSender {
    protected String message;
    protected long timestamp;

    public AbstractMessageSender(String message) {
        this.message = message;
        this.timestamp = System.currentTimeMillis();
    }

    public abstract void send();              // 抽象方法——没有方法体
    public void logTime() {                   // 具体方法
        System.out.println("[LOG] " + timestamp);
    }
}

做了两个改变:① 类声明加 abstract——不能 new了;② send() 去掉方法体加 abstract——子类必须实现。

忘记重写会编译报错:

java
// 编译错误:Pigeon is not abstract and does not override abstract method send()
public class Pigeon extends AbstractMessageSender {
    public Pigeon(String message) { super(message); }
    // 忘了重写 send()
}

你试试new AbstractMessageSender("hello")——编译器报错,抽象类不能实例化。

抽象类是"讲故事讲一半"——"我有 send 方法,但具体怎么做——你(子类)自己定。"


第二幕:接口——纯契约

抽象类还有一个矛盾:一个类只能 extends 一个父类。如果侦察兵已经继承了 Soldier,就不能再继承 AbstractMessageSender

那能不能把"能发送消息"这个能力从"类"中独立出来,变成一种纯契约

接口 (interface) 就是干这个的:

java
// Sendable.java — 接口定义
public interface Sendable {
    void send(); // 抽象方法,默认 public abstract
}

语言:Java 17+

任何类都可以实现这个接口:

java
public class Pigeon extends AbstractMessageSender implements Sendable {
    public Pigeon(String message) { super(message); }

    @Override
    public void send() {
        logTime();
        System.out.println(" 信鸽起飞!消息: " + message);
    }
}

一个类只能有一个父类,但可以有多个接口

java
public class Scout extends Soldier implements Sendable, Receivable, Trackable {
    // ...
}

这就是接口的强大之处——抽象了"能做什么",而不是"是什么"

Java 8+ 接口的新能力

Java 8 之后,接口可以包含默认方法:

java
public interface Sendable {
    void send(); // 抽象方法(必须实现)

    default void logTime() { // 默认方法——接口里也可以有实现了
        System.out.println("[LOG] " + System.currentTimeMillis());
    }

    static boolean isValidMessage(String msg) { // 静态方法
        return msg != null && !msg.isEmpty();
    }
}

语言:Java 8+ 你试试:创建一个类实现 Sendable,但不重写 logTime()——看看是否能直接调用默认方法。

默认方法解决了"接口升级时所有实现类都要改"的问题。


第三幕:抽象类 vs 接口——怎么选?

对比维度抽象类接口
关键字abstract classinterface
构造器可以有不能有
字段可以有各种访问级别只能是 public static final(常量)
方法实现可以有具体方法Java 8+ 可以有 defaultstatic 方法
继承限制一个类只能继承一个一个类可以实现多个
核心用途共享状态 + 部分实现定义能力契约

经验法则:如果两个类共享状态(字段)和部分实现 → 抽象类。如果只是定义能力契约 → 接口。


第四幕:Object 类——所有类的根

每个类都隐含地继承了 java.lang.Object,即使你没写 extends

java
public class Horse { ... }
// 等价于:public class Horse extends java.lang.Object { ... }

Object 提供了几个基本方法:

java
Object obj = new Horse("敌军", "张三");
obj.toString();   // "Horse@6d06d69c"
obj.equals(another);  // 默认比较引用
obj.hashCode();
obj.getClass();   // 运行时获取实际类型

重写 toString() 让调试更方便:

java
@Override
public String toString() {
    return "Horse{message='" + message + "'}";
}

旅人笔记

抽象类说"我有骨架,你来填"。 接口说"我只要你遵守契约"。 抽象类可以有构造器和状态,接口不能。 一个类实现多个接口,只继承一个父类。 所有类的老祖宗是 Object

下一站预告

你学会了接口和抽象类来定义契约。现在做一个综合练习巩固一下——然后开启函数式编程的大门,用 Lambda 把行为像包裹一样递过去。

Built with VitePress | Software Systems Atlas