接口 vs 抽象类

65 阅读6分钟

Java 接口与抽象类详解

🎯 核心理念

  • 抽象类:描述"是什么" (is-a关系),更多是减少重复代码;
  • 接口:描述"能做什么" (can-do关系),更多是插件可插拔、规范函数命名;
public interface IProcess<T> {
    /**
     * 准入
     *
     * @return
     */
    boolean access(T t);

    /**
     * 具体操作
     */
    void doProcess(T t) throws Exception;

    /**
     * 名称
     */
    String name();
}
@Service
public class xxxProcess implements IProcess<OnlineContext> {
    private static final Logger LOGGER = LoggerFactory.getLogger(xxxProcess.class);

    @Override
    public boolean access(OnlineContext onlineContext) {
        return true;
    }

    @Override
    public void doProcess(OnlineContext onlineContext) throws Exception {
        return;
    }

    @Override
    public String name() {
        return "xxx";
    }
}

📋 基础对比

特性抽象类接口
关键字abstract classinterface
继承方式单继承 (extends)多实现 (implements)
方法类型具体方法 + 抽象方法抽象方法 + 默认方法 + 静态方法
变量各种类型变量只能有常量 (public static final)
构造器可以有构造器不能有构造器
访问修饰符任意访问修饰符方法默认 public

🔧 抽象类详解

基本语法

abstract class Animal {
    protected String name;
    
    // 构造器
    public Animal(String name) {
        this.name = name;
    }
    
    // 具体方法:所有动物都这样吃饭
    public void eat() {
        System.out.println(name + " 正在吃饭");
    }
    
    // 抽象方法:每种动物叫声不同,子类必须实现
    public abstract void makeSound();
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + " 汪汪叫");
    }
}

使用场景

  1. 有共同代码需要复用
  2. 关系是"是什么"
  3. 需要定义构造器
abstract class Vehicle {
    protected String brand;
    protected String model;
    
    public Vehicle(String brand, String model) {
        this.brand = brand;
        this.model = model;
    }
    
    // 共同的启动流程
    public void start() {
        System.out.println("检查燃料...");
        System.out.println("启动引擎...");
        startEngine(); // 具体启动方式由子类决定
    }
    
    protected abstract void startEngine();
}

class Car extends Vehicle {
    public Car(String brand, String model) {
        super(brand, model);
    }
    
    @Override
    protected void startEngine() {
        System.out.println("汽车引擎启动");
    }
}

🔌 接口详解

Java 8 接口特性

1. 抽象方法(必须实现)
interface PaymentProcessor {
    boolean processPayment(double amount);
    PaymentResult getPaymentStatus(String transactionId);
}
2. 默认方法(可选实现)
interface PaymentProcessor {
    boolean processPayment(double amount);
    
    // 默认方法:可以不重写
    default void logTransaction(double amount) {
        System.out.println("交易记录: ¥" + amount + " at " + new Date());
    }
    
    default boolean validateAmount(double amount) {
        return amount > 0 && amount <= 50000;
    }
}
3. 静态方法(工具方法)
interface PaymentProcessor {
    boolean processPayment(double amount);
    
    // 静态方法:直接通过接口调用
    static String generateTransactionId() {
        return "TXN_" + System.currentTimeMillis();
    }
    
    static boolean isValidCardNumber(String cardNumber) {
        return cardNumber != null && cardNumber.length() == 16;
    }
}

🎯 接口的真正作用

1. 制定统一标准(规范)

想象你是个老板,要招聘司机:

// 没有接口的情况
class TruckDriver {
    public void driveTruck() { /* 开卡车 */ }
}

class TaxiDriver {
    public void driveTaxi() { /* 开出租车 */ }
}

// 老板很头疼:每种司机方法名都不一样!
// 有接口的情况
interface Driver {
    void drive();  // 统一标准:所有司机都必须会"开车"
    void park();   // 统一标准:所有司机都必须会"停车"
}

class TruckDriver implements Driver {
    public void drive() { System.out.println("开卡车"); }
    public void park() { System.out.println("停卡车"); }
}

class TaxiDriver implements Driver {
    public void drive() { System.out.println("开出租车"); }
    public void park() { System.out.println("停出租车"); }
}

// 老板开心了:所有司机都有统一的方法名!
public class Boss {
    public void hireDriver(Driver driver) {
        driver.drive(); // 不管什么司机,都用同样的方法调用
        driver.park();
    }
}


====


static void driverExample() {
    System.out.println("🚗 老板要雇佣不同类型的司机");

    Boss boss = new Boss();

    // 创建不同类型的司机
    Driver truckDriver = new TruckDriver();
    Driver taxiDriver = new TaxiDriver();
    Driver busDriver = new BusDriver(); // 新增的公交司机

    System.out.println("\n🔥 关键点:老板用同样的方法调用所有司机!");

    // 🔥 老板用同一个方法处理所有司机
    boss.hireDriver(truckDriver);   // 传入卡车司机
    System.out.println();

    boss.hireDriver(taxiDriver);    // 传入出租车司机
    System.out.println();

    boss.hireDriver(busDriver);     // 传入公交司机
    System.out.println();

    System.out.println("💡 看到了吗?老板的 hireDriver 方法完全没变,但能处理所有类型的司机!");
}

2. 实现多态和解耦

这是接口最重要的作用!

// 支付系统例子
interface PaymentProcessor {
    boolean processPayment(double amount);
}

class AlipayProcessor implements PaymentProcessor {
    public boolean processPayment(double amount) {
        System.out.println("通过支付宝支付: " + amount);
        return true;
    }
}

class WechatProcessor implements PaymentProcessor {
    public boolean processPayment(double amount) {
        System.out.println("通过微信支付: " + amount);
        return true;
    }
}

// 订单系统不需要知道具体用哪种支付方式
class OrderService {
    private PaymentProcessor processor;
    
    public OrderService(PaymentProcessor processor) {
        this.processor = processor; // 可以是任何实现了接口的支付方式
    }
    
    public void checkout(double amount) {
        if (processor.processPayment(amount)) {
            System.out.println("订单完成!");
        }
    }
}

// 使用时的灵活性
public class Main {
    public static void main(String[] args) {
        // 今天用支付宝
        OrderService order1 = new OrderService(new AlipayProcessor());
        order1.checkout(100);
        
        // 明天换微信支付,OrderService代码不用改!
        OrderService order2 = new OrderService(new WechatProcessor());
        order2.checkout(200);
    }
}

3. 插件化架构

接口让系统可以轻松扩展:

interface MessageSender {
    void sendMessage(String message);
}

class EmailSender implements MessageSender {
    public void sendMessage(String message) {
        System.out.println("发送邮件: " + message);
    }
}

class SMSSender implements MessageSender {
    public void sendMessage(String message) {
        System.out.println("发送短信: " + message);
    }
}

// 后来新增钉钉通知,只需要新增类,不用修改原有代码
class DingTalkSender implements MessageSender {
    public void sendMessage(String message) {
        System.out.println("发送钉钉消息: " + message);
    }
}

class NotificationService {
    private List<MessageSender> senders;
    
    public void notifyAll(String message) {
        for (MessageSender sender : senders) {
            sender.sendMessage(message); // 统一调用方式
        }
    }
}

🔥 如果没有接口会怎样?

// 没有接口的痛苦版本
class OrderService {
    private String paymentType;
    
    public void checkout(double amount) {
        if (paymentType.equals("alipay")) {
            // 支付宝支付逻辑
            System.out.println("通过支付宝支付: " + amount);
        } else if (paymentType.equals("wechat")) {
            // 微信支付逻辑
            System.out.println("通过微信支付: " + amount);
        } else if (paymentType.equals("bank")) {
            // 银行卡支付逻辑
            System.out.println("通过银行卡支付: " + amount);
        }
        // 每次新增支付方式都要修改这里!违反了开闭原则
    }
}

问题

  • 每次新增支付方式都要修改OrderService
  • 代码越来越臃肿
  • 测试困难
  • 团队协作困难(你改支付宝,我改微信,容易冲突)

🤔 何时使用哪种?

使用抽象类的情况

  • ✅ 有共同的代码要复用
  • ✅ 关系是"是什么"(is-a)
  • ✅ 需要构造器
  • ✅ 需要保护成员变量
abstract class DatabaseConnection {
    protected String url;
    protected String username;
    
    public DatabaseConnection(String url, String username) {
        this.url = url;
        this.username = username;
    }
    
    // 通用连接流程
    public void connect() {
        validateConnection();
        establishConnection();
        System.out.println("连接建立成功");
    }
    
    protected abstract void establishConnection();
    
    private void validateConnection() {
        System.out.println("验证连接参数...");
    }
}

使用接口的情况

  • ✅ 定义能力或行为契约
  • ✅ 需要多重继承
  • ✅ 关系是"能做什么"(can-do)
  • ✅ 插件化架构
  • ✅ 制定统一规范
interface Flyable {
    void fly();
}

interface Swimmable {
    void swim();
}

// 鸭子既能飞又能游泳
class Duck extends Animal implements Flyable, Swimmable {
    @Override
    public void fly() {
        System.out.println("鸭子扑腾翅膀飞行");
    }
    
    @Override
    public void swim() {
        System.out.println("鸭子在水中游泳");
    }
}

🎯 最佳实践

1. 组合使用

abstract class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
    
    public void sleep() {
        System.out.println(name + " 在睡觉");
    }
}

interface Flyable {
    void fly();
}

class Bird extends Animal implements Flyable {
    public Bird(String name) {
        super(name);
    }
    
    @Override
    public void fly() {
        System.out.println(name + " 在飞翔");
    }
}

2. 接口优先原则

  • 优先考虑接口,更灵活
  • 有共同代码时才用抽象类
  • 可以一个类继承抽象类同时实现多个接口

3. 函数式接口

@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);
    
    // 可以有默认方法
    default void printResult(int a, int b) {
        System.out.println("结果: " + calculate(a, b));
    }
    
    // 可以有静态方法
    static Calculator add() {
        return (a, b) -> a + b;
    }
}

// Lambda表达式
Calculator multiply = (a, b) -> a * b;
Calculator divide = (a, b) -> a / b;

💡 记忆口诀

  • 抽象类:家族遗传,有共同基因(代码复用)
  • 接口:能力证书,规定技能(行为规范)
  • 接口:USB插口,即插即用(插件架构)

核心思想:接口定义"做什么",抽象类定义"是什么",具体类定义"怎么做"!