问题

接口和抽象类的区别,如何选择?

答案

1. 核心区别对比

维度 接口(Interface) 抽象类(Abstract Class)
关键字 interface abstract class
继承/实现 一个类可实现多个接口 一个类只能继承一个抽象类
方法 JDK 8前只能是抽象方法
JDK 8+可有默认方法和静态方法
可以有抽象方法和具体方法
成员变量 只能是public static final常量 可以有任意类型的成员变量
构造方法 不能有构造方法 可以有构造方法
访问修饰符 方法默认public abstract
JDK 9+可有private方法
可以有任意访问修饰符
设计目的 定义行为规范(What to do) 定义模板和共性(Is-A关系)
使用场景 不相关类实现相同行为 有共同父类的类层次

2. 接口详解

基本语法

// JDK 8之前的接口
public interface Flyable {
    // 常量(默认public static final)
    int MAX_SPEED = 1000;

    // 抽象方法(默认public abstract)
    void fly();
    void land();
}

JDK 8接口增强

public interface Flyable {
    // 抽象方法
    void fly();

    // 默认方法(default)
    default void land() {
        System.out.println("Landing...");
    }

    // 静态方法
    static void checkWeather() {
        System.out.println("Checking weather...");
    }
}

JDK 9接口增强

public interface Flyable {
    void fly();

    default void performFlight() {
        checkFuel();  // 调用private方法
        fly();
    }

    // private方法(JDK 9+)
    private void checkFuel() {
        System.out.println("Checking fuel...");
    }

    // private静态方法
    private static void log(String msg) {
        System.out.println("Log: " + msg);
    }
}

3. 抽象类详解

public abstract class Animal {
    // 成员变量(可以是任意类型)
    private String name;
    protected int age;

    // 构造方法
    public Animal(String name) {
        this.name = name;
    }

    // 抽象方法(子类必须实现)
    public abstract void makeSound();

    // 具体方法(子类可直接使用)
    public void sleep() {
        System.out.println(name + " is sleeping");
    }

    // Getter/Setter
    public String getName() {
        return name;
    }
}

// 子类
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(getName() + " barks");
    }
}

4. 设计原则视角

接口:定义能力(Can-Do关系)

// 定义"会飞"的能力
public interface Flyable {
    void fly();
}

// 定义"会游泳"的能力
public interface Swimmable {
    void swim();
}

// 鸟类可以飞
public class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("Bird flies");
    }
}

// 鸭子既会飞又会游
public class Duck implements Flyable, Swimmable {
    @Override
    public void fly() {
        System.out.println("Duck flies");
    }

    @Override
    public void swim() {
        System.out.println("Duck swims");
    }
}

抽象类:定义本质(Is-A关系)

// 定义动物的本质
public abstract class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    // 所有动物都有的共性行为
    public void breathe() {
        System.out.println(name + " is breathing");
    }

    // 不同动物的差异行为
    public abstract void makeSound();
}

// 狗是一种动物
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + " barks");
    }
}

5. 如何选择?

选择接口的场景

多种不相关的类需要实现相同行为

// Serializable接口:String、ArrayList、HashMap都可序列化
public class User implements Serializable { }
public class Order implements Serializable { }

需要多重继承能力

public class Smartphone implements Callable, Camera, MusicPlayer {
    // 手机同时具备打电话、拍照、播放音乐的能力
}

定义服务契约(API设计)

// Service层接口
public interface UserService {
    User getUserById(Long id);
    void saveUser(User user);
}

// 可以有多种实现
public class DatabaseUserService implements UserService { }
public class CachedUserService implements UserService { }

选择抽象类的场景

需要共享代码(状态和行为)

public abstract class HttpServlet {
    // 共享的成员变量
    protected ServletConfig config;

    // 模板方法
    public void service(HttpRequest req, HttpResponse res) {
        if ("GET".equals(req.getMethod())) {
            doGet(req, res);
        } else if ("POST".equals(req.getMethod())) {
            doPost(req, res);
        }
    }

    // 子类实现具体逻辑
    protected abstract void doGet(HttpRequest req, HttpResponse res);
    protected abstract void doPost(HttpRequest req, HttpResponse res);
}

需要构造函数初始化

public abstract class Vehicle {
    private String brand;

    // 构造方法强制子类初始化
    public Vehicle(String brand) {
        this.brand = brand;
    }

    public abstract void start();
}

需要非public成员

public abstract class BaseDao<T> {
    // protected方法,仅子类可访问
    protected Connection getConnection() {
        // 获取数据库连接
    }

    public abstract T findById(Long id);
}

6. 实际应用案例

案例1:JDK中的设计

// 接口:定义能力
List<String> list = new ArrayList<>();  // List是接口
Map<String, Object> map = new HashMap<>();  // Map是接口

// 抽象类:提供通用实现
public abstract class AbstractList<E> implements List<E> {
    // 提供通用的add、remove等方法实现
}

public class ArrayList<E> extends AbstractList<E> {
    // 继承通用实现,专注于自己的特性
}

案例2:Spring框架

// 接口定义服务契约
public interface UserService {
    User findById(Long id);
}

// 抽象类提供事务、日志等通用功能
public abstract class BaseService {
    @Autowired
    protected Logger logger;

    protected void logOperation(String operation) {
        logger.info("Executing: " + operation);
    }
}

// 实现类
@Service
public class UserServiceImpl extends BaseService implements UserService {
    @Override
    public User findById(Long id) {
        logOperation("findById: " + id);
        // 具体实现
    }
}

案例3:模板方法模式

// 抽象类定义算法骨架
public abstract class DataProcessor {
    // 模板方法(final防止被重写)
    public final void process() {
        readData();
        processData();
        writeData();
    }

    // 具体步骤由子类实现
    protected abstract void readData();
    protected abstract void processData();
    protected abstract void writeData();
}

public class CsvDataProcessor extends DataProcessor {
    @Override
    protected void readData() {
        System.out.println("Reading CSV data");
    }

    @Override
    protected void processData() {
        System.out.println("Processing CSV data");
    }

    @Override
    protected void writeData() {
        System.out.println("Writing CSV data");
    }
}

7. JDK 8+的接口变化影响

接口与抽象类的界限模糊

// JDK 8后,接口也可以有方法实现
public interface Flyable {
    // 抽象方法
    void fly();

    // 默认方法(有实现)
    default void takeOff() {
        System.out.println("Taking off...");
        fly();
    }

    // 静态方法
    static void printManual() {
        System.out.println("Flight manual...");
    }
}

接口的优势进一步增强:

  • 可以有默认实现,减少子类重复代码
  • 保持多重继承的能力
  • 更适合函数式编程(Lambda表达式)

8. 选择决策树

需要定义行为规范?
  ├─ 是 → 考虑接口
  └─ 否 → 考虑抽象类

需要多重继承?
  ├─ 是 → 必须用接口
  └─ 否 → 可用抽象类

需要共享状态(成员变量)?
  ├─ 是 → 抽象类
  └─ 否 → 接口

需要构造方法?
  ├─ 是 → 抽象类
  └─ 否 → 接口

实现类之间是Is-A关系?
  ├─ 是 → 抽象类
  └─ 否 → 接口

9. 面试答题要点

标准回答结构:

  1. 核心区别
    • 接口:定义行为规范,支持多实现,JDK 8+可有默认方法
    • 抽象类:定义模板,共享代码,支持构造方法和成员变量
  2. 选择原则
    • 接口:多重继承、定义能力、服务契约
    • 抽象类:代码复用、模板方法、Is-A关系
  3. JDK版本影响:JDK 8引入default和static方法,接口更强大

  4. 实际案例:JDK集合框架、Spring框架、模板方法模式

加分点:

  • 说明JDK 8/9对接口的增强
  • 了解设计原则(Is-A vs Can-Do)
  • 能举实际项目中的应用案例
  • 知道接口与抽象类可以配合使用

10. 总结

记忆口诀:

  • 接口定能力,抽象定本质
  • 接口多实现,抽象单继承
  • 接口轻契约,抽象重复用
  • JDK8后,接口更强大

最佳实践:

  • 优先使用接口(灵活性更高)
  • 需要代码复用时用抽象类
  • 接口 + 抽象类组合使用效果最佳
// 推荐模式:接口定义契约 + 抽象类提供通用实现
public interface List<E> { }  // 接口

public abstract class AbstractList<E> implements List<E> { }  // 抽象类

public class ArrayList<E> extends AbstractList<E> { }  // 具体类