--- title: "02-抽象类与接口" created: 2025-11-25 tags: - Java --- # 抽象类与接口 ## 核心概念 抽象类(Abstract Class)和接口(Interface)都是Java中定义抽象层次的方式,但设计思想和使用场景不同。理解它们的区别和演进,是设计良好架构的基础。 --- ## 一、抽象类(Abstract Class) ### 1.1 基本语法 ```java public abstract class Animal { // 普通字段 protected String name; // 构造函数(不能直接实例化,但子类可以调用) public Animal(String name) { this.name = name; } // 普通方法 public void sleep() { System.out.println(name + " is sleeping"); } // 抽象方法(没有方法体,子类必须实现) public abstract void speak(); } // 子类必须实现所有抽象方法 public class Dog extends Animal { public Dog(String name) { super(name); } @Override public void speak() { System.out.println(name + " says: Woof!"); } } ``` ### 1.2 抽象类的特点 | 特点 | 说明 | | --- | --- | | **不能实例化** | `new Animal()` 编译错误 | | **可以有构造函数** | 供子类调用 | | **可以有普通方法** | 提供默认实现 | | **可以有抽象方法** | 子类必须实现 | | **可以有字段** | 任意访问修饰符 | | **单继承** | 只能extends一个类 | ### 1.3 抽象类的设计意图 ```java // 模板方法模式:定义算法骨架,子类实现具体步骤 public abstract class AbstractOrderProcessor { // 模板方法(final防止子类修改流程) public final void processOrder(Order order) { validateOrder(order); calculatePrice(order); applyDiscount(order); // 抽象方法,子类实现 saveOrder(order); sendNotification(order); // 钩子方法,子类可选重写 } private void validateOrder(Order order) { /* 通用验证 */ } private void calculatePrice(Order order) { /* 通用计算 */ } private void saveOrder(Order order) { /* 通用保存 */ } // 抽象方法:子类必须实现 protected abstract void applyDiscount(Order order); // 钩子方法:子类可选重写 protected void sendNotification(Order order) { // 默认实现:发送邮件 } } // 具体实现 public class VIPOrderProcessor extends AbstractOrderProcessor { @Override protected void applyDiscount(Order order) { order.setDiscount(0.8); // VIP 8折 } @Override protected void sendNotification(Order order) { // VIP专属通知 } } ``` --- ## 二、接口(Interface) ### 2.1 JDK 7及之前 ```java public interface Flyable { // 常量(默认 public static final) int MAX_SPEED = 1000; // 抽象方法(默认 public abstract) void fly(); void land(); } // 实现类 public class Bird implements Flyable { @Override public void fly() { System.out.println("Bird is flying"); } @Override public void land() { System.out.println("Bird is landing"); } } ``` ### 2.2 JDK 8接口增强 ```java public interface Flyable { // 抽象方法 void fly(); // 默认方法(default):提供默认实现,实现类可选重写 default void takeOff() { System.out.println("Taking off..."); } // 静态方法(static):通过接口名调用 static void showInfo() { System.out.println("This is Flyable interface"); } } // 使用 Bird bird = new Bird(); bird.fly(); // 实现类方法 bird.takeOff(); // 默认方法 Flyable.showInfo(); // 静态方法 ``` ### 2.3 JDK 9接口增强 ```java public interface Flyable { void fly(); default void takeOff() { prepare(); // 调用私有方法 System.out.println("Taking off..."); } default void land() { System.out.println("Landing..."); cleanup(); // 调用私有方法 } // 私有方法(private):提取公共逻辑,仅接口内部使用 private void prepare() { System.out.println("Preparing..."); } private void cleanup() { System.out.println("Cleaning up..."); } // 私有静态方法 private static void log(String msg) { System.out.println("[LOG] " + msg); } } ``` ### 2.4 接口演进总结 | 版本 | 新增特性 | 说明 | | --- | --- | --- | | JDK 7 | 常量、抽象方法 | 纯抽象 | | JDK 8 | default方法、static方法 | 可以有实现 | | JDK 9 | private方法、private static方法 | 代码复用 | --- ## 三、抽象类 vs 接口 ### 3.1 核心区别 | 特性 | 抽象类 | 接口 | | --- | --- | --- | | **继承** | 单继承 | 多实现 | | **构造函数** | 可以有 | 不能有 | | **字段** | 任意类型 | 只能public static final | | **方法** | 任意类型 | public(JDK 9+可private) | | **设计思想** | is-a(是什么) | can-do(能做什么) | | **演进成本** | 低(可加普通方法) | 高(JDK 8前加方法会破坏实现类) | ### 3.2 设计思想对比 **接口更像是一种行为规范,强调“能做什么”**;**抽象类更像是一种模板,强调“应该怎么做”**。 从设计思路上来说:**接口通常是自上而下的设计**,我们先定义好一个行为契约(比如 Runnable),然后不同的类根据自己的能力去实现它。这种方式适合“能力横切”的场景(像排序、序列化),这些类之间未必有继承关系。**这种设计强调先约定行为,再去实现,面向接口编程就是这个思想的体现**。 而**抽象类的设计更偏向于自下而上**,我们往往是写了一批类之后,发现它们之间有一些通用逻辑或状态,于是把这些共性抽象出来,形成一个抽象父类,减少重复代码。比如所有动物都有名字、有吃饭的方法,那就可以抽出一个 Animal 抽象类。 **另外一个比较关键区别在于:Java 支持** 接口的多实现**,不支持抽象类的多继承。** 这是因为 Java 要避免多继承带来的复杂性,特别是菱形继承问题,这种问题很容易引起成员冲突和继承歧义,所以一个类只能继承一个抽象类。但接口就没这种问题,接口里没有普通的字段状态,它的变量都是 `static final` 的,也不依赖构造方法,天生就是用来声明行为的,所以可以允许多实现,不会有状态冲突,一个类可以实现多个接口。(没有状态、不依赖构造器,自然避免多继承冲突) 从语法角度补充几点: - 接口只能用 implements 关键字来实现,抽象类只能用 extends 关键字来单继承。 - 接口里的方法默认是 public 的,字段是 public static final 的。 - 抽象类可以有各种访问修饰符、普通字段和构造器。 - 抽象类里可以写通用逻辑,接口主要声明行为规范,在 Java 8+ 得到了增强(default、static、private 方法),在保留行为约定特性的同时也具备了部分逻辑复用能力,但依然不能有构造函数,不能持有实例状态,这也是它与抽象类的根本区别。 在选择上: - 如果是多个无关类要统一某个能力,优先接口(如 Comparable、Serializable)。 - 如果是多个相关类共享部分代码逻辑,适合抽象类。 一句话总结:**接口强调“行为契约”,适合多实现;抽象类强调“逻辑复用”,只适合单继承。** ```java // 抽象类:is-a 关系(是什么) abstract class Animal { protected String name; public abstract void speak(); } class Dog extends Animal { // Dog is an Animal } // 接口:can-do 关系(能做什么) interface Flyable { void fly(); } interface Swimmable { void swim(); } class Duck extends Animal implements Flyable, Swimmable { // Duck is an Animal // Duck can fly // Duck can swim } ``` ### 3.3 选择原则 ``` 需要定义一组行为规范? ├── 是 → 接口 └── 否 → 需要共享代码或状态? ├── 是 → 抽象类 └── 否 → 需要多继承? ├── 是 → 接口 └── 否 → 根据语义选择 ``` **选择接口的场景:** - 定义能力/行为(Comparable、Serializable) - 需要多继承 - 不相关的类需要实现相同行为 **选择抽象类的场景:** - 有共享的代码或状态 - 需要非public成员 - 需要构造函数 - 紧密相关的类之间共享实现 ### 3.4 组合使用 ```java // 接口定义能力 public interface Drawable { void draw(); } // 抽象类提供部分实现 public abstract class Shape implements Drawable { protected String color; public Shape(String color) { this.color = color; } public String getColor() { return color; } // 留给子类实现 @Override public abstract void draw(); } // 具体类 public class Circle extends Shape { private double radius; public Circle(String color, double radius) { super(color); this.radius = radius; } @Override public void draw() { System.out.println("Drawing " + color + " circle with radius " + radius); } } ``` --- ## 四、接口的默认方法冲突 ### 4.1 类优先原则 ```java interface A { default void method() { System.out.println("A"); } } class B { public void method() { System.out.println("B"); } } class C extends B implements A { // 不需要重写,类的方法优先 } new C().method(); // 输出:B ``` ### 4.2 接口冲突 ```java interface A { default void method() { System.out.println("A"); } } interface B { default void method() { System.out.println("B"); } } // 必须重写解决冲突 class C implements A, B { @Override public void method() { A.super.method(); // 选择调用A的默认方法 // 或 B.super.method(); // 或自己实现 } } ``` ### 4.3 冲突解决规则 1. **类优先**:类中的方法优先于接口默认方法 2. **子接口优先**:更具体的接口优先 3. **显式选择**:如果仍有冲突,必须重写并显式选择 --- ## 五、函数式接口 ### 5.1 定义 ```java // 只有一个抽象方法的接口 @FunctionalInterface public interface Comparator { int compare(T o1, T o2); // 可以有默认方法 default Comparator reversed() { return (o1, o2) -> compare(o2, o1); } // 可以有静态方法 static > Comparator naturalOrder() { return (o1, o2) -> o1.compareTo(o2); } } ``` ### 5.2 常用函数式接口 | 接口 | 方法 | 用途 | | --- | --- | --- | | `Runnable` | `void run()` | 无参无返回 | | `Supplier` | `T get()` | 无参有返回 | | `Consumer` | `void accept(T t)` | 有参无返回 | | `Function` | `R apply(T t)` | 有参有返回 | | `Predicate` | `boolean test(T t)` | 判断 | ### 5.3 Lambda表达式 ```java // 传统写法 Comparator c1 = new Comparator() { @Override public int compare(String o1, String o2) { return o1.length() - o2.length(); } }; // Lambda写法 Comparator c2 = (o1, o2) -> o1.length() - o2.length(); // 方法引用 Comparator c3 = Comparator.comparingInt(String::length); ``` --- ## 六、高频面试题 ### Q1: 抽象类和接口有什么区别? **答:** | 特性 | 抽象类 | 接口 | | --- | --- | --- | | 继承 | 单继承 | 多实现 | | 构造函数 | 有 | 无 | | 字段 | 任意 | public static final | | 设计思想 | is-a | can-do | ### Q2: JDK 8对接口做了哪些增强? **答:** - **default方法**:提供默认实现,解决接口演进问题 - **static方法**:工具方法,通过接口名调用 - **函数式接口**:支持Lambda表达式 ### Q3: 什么时候用抽象类,什么时候用接口? **答:** - **使用接口**: 1. 定义行为规范 2. 需要多继承 3. 不相关的类需要共享行为 4. 轻量级的行为定义 - **使用抽象类**: 1. 需要共享代码和状态 2. 有共同的基础实现 3. 需要非public成员 4. 需要构造函数 5. 紧密相关的类 ### Q4: 接口的默认方法解决了什么问题? **答:** - **演进问题**:在不破坏现有实现的情况下,为接口添加新方法 - **代码复用**:避免在所有实现类中重复编写相同逻辑 - **减少抽象类使用**:提供类似抽象类的代码共享能力 ### Q5: 为什么Java不支持多继承? **答:** - **菱形继承问题**:多个父类有同名方法,子类无法确定调用哪个 - **复杂性**:增加语言复杂度和编译器实现难度 - **替代方案**:接口 + 组合模式 ### Q6: 函数式接口的作用是什么? **答:** - 只有一个抽象方法的接口 - 支持Lambda表达式和方法引用 - 简化匿名内部类的写法 - 函数式编程的基础 ### Q7: 接口的静态方法和默认方法有什么区别? **答:** - **静态方法**: 1. 通过接口名调用 2. 不能被实现类重写 3. 属于接口本身 - **默认方法**: 1. 实例方法 2. 可以被实现类重写 3. 提供默认实现 --- ## 七、最佳实践 ### ✅ 推荐做法 ```java // 1. 接口定义清晰的行为规范 public interface Playable { void play(); // 提供通用实现 default void pause() { System.out.println("Pausing..."); } } // 2. 抽象类提供通用实现 public abstract class AbstractPlayer implements Playable { protected String name; public AbstractPlayer(String name) { this.name = name; } // 部分实现 public void displayInfo() { System.out.println("Player: " + name); } } // 3. 组合优于继承 public class MusicPlayer { private Playable device; // 组合 public void setDevice(Playable device) { this.device = device; } } // 4. 使用函数式接口简化代码 public interface Validator { boolean validate(T t); // 组合验证 default Validator and(Validator other) { return t -> this.validate(t) && other.validate(t); } } // Lambda表达式 Validator notEmpty = s -> !s.isEmpty(); Validator minLength = s -> s.length() >= 6; Validator passwordValidator = notEmpty.and(minLength); ``` ### ❌ 避免做法 ```java // 1. 过度使用接口 public interface Everything { void doSomething(); void doSomethingElse(); // 接口膨胀,职责不清 } // 2. 滥用默认方法 public interface DatabaseOperation { default void connect() { // 太多细节实现 } default void disconnect() { // 接口不应该包含太多实现 } } // 3. 忽视接口冲突 public class BadImplementation implements A, B { // 没有解决方法冲突 } ``` --- ## 八、设计模式应用 ### 8.1 策略模式 ```java // 定义策略接口 public interface PaymentStrategy { void pay(int amount); } // 具体策略实现 public class CreditCardPayment implements PaymentStrategy { @Override public void pay(int amount) { System.out.println("Paying " + amount + " by credit card"); } } public class PayPalPayment implements PaymentStrategy { @Override public void pay(int amount) { System.out.println("Paying " + amount + " by PayPal"); } } // 上下文类 public class PaymentContext { private PaymentStrategy strategy; public void setStrategy(PaymentStrategy strategy) { this.strategy = strategy; } public void executePayment(int amount) { strategy.pay(amount); } } // 使用 PaymentContext context = new PaymentContext(); context.setStrategy(new CreditCardPayment()); context.executePayment(100); ``` ### 8.2 模板方法模式 ```java // 抽象类定义算法骨架 public abstract class DataProcessor { // 模板方法 public final void process() { readData(); processData(); saveData(); } // 公共方法 private void readData() { System.out.println("Reading data..."); } private void saveData() { System.out.println("Saving data..."); } // 抽象方法,子类实现 protected abstract void processData(); } // 具体实现 public class CSVProcessor extends DataProcessor { @Override protected void processData() { System.out.println("Processing CSV data"); } } public class JSONProcessor extends DataProcessor { @Override protected void processData() { System.out.println("Processing JSON data"); } } ``` --- ## 相关链接 - [[01-继承与多态|01-继承与多态]] - [[03-访问权限|03-访问权限]] - 设计模式(结合使用的先例可先看 [[02-抽象类与接口的结合|抽象类与接口的结合]] 的模板方法) --- ## 记忆口诀 ``` 抽象类可有状态,接口定义能力 默认方法解演进,静态方法类级别 单继承多实现,is-a和can-do分明 组合优于继承,设计模式显神明 ``` ## 相关笔记 本篇是主战场(语法+设计取舍);[[01-抽象类与接口的区别|抽象类与接口的区别]] 做了逐条对比速记,[[02-抽象类与接口的结合|抽象类与接口的结合]] 补了模板方法模式与选型标准,可以当本篇的延伸阅读。 --- ⬅️ [[01-继承与多态|继承与多态]] 🏠 [[00-Java|00-Java]] ➡️ [[03-访问权限|访问权限]]