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 class | interface |
| 继承方式 | 单继承 (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 + " 汪汪叫");
}
}
使用场景
- 有共同代码需要复用
- 关系是"是什么"
- 需要定义构造器
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插口,即插即用(插件架构)
核心思想:接口定义"做什么",抽象类定义"是什么",具体类定义"怎么做"!