抽象类 vs 接口
刚学 Java 的时候,很多人都会被这个问题困扰:抽象类和接口到底用哪个?
其实只要记住一个原则:抽象类是「是什么」,接口是「能做什么」。
本质区别
| 对比项 | 抽象类 | 接口 |
|---|---|---|
| 关键字 | abstract class | interface |
| 继承 | 单继承 | 多实现 |
| 构造方法 | 可以有 | 不能有 |
| 方法 | 抽象 + 普通都可以 | JDK 7 前只能是抽象方法 |
| 字段 | 无限制 | 只能是常量 (static final) |
| 访问修饰符 | 任意 | 默认 public |
| 静态方法 | 可以有 | JDK 8+ 可以有 |
| 用途 | 「是什么」 | 「能做什么」 |
什么时候用抽象类
建立 is-a 关系
当你在描述「X 是一种 Y」时,用抽象类:
java
// Dog 是一种 Animal
abstract class Animal {
String name;
abstract void eat();
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("狗在吃狗粮");
}
}需要共享代码
当多个子类需要共享一些通用实现时,用抽象类:
java
abstract class Employee {
protected String name;
protected double baseSalary;
// 所有员工都要打卡
public void clockIn() {
System.out.println(name + "打卡上班");
}
// 计算工资:子类必须实现
public abstract double calculateSalary();
}
class FullTimeEmployee extends Employee {
@Override
public double calculateSalary() {
return baseSalary + 1000;
}
}需要非 public 成员
如果需要 protected、private 或包级访问的成员,只能用抽象类:
java
abstract class Base {
protected void helperMethod() { } // 只有子类能用
private void privateHelper() { } // 内部辅助方法
}什么时候用接口
建立 can-do 关系
当你在描述「X 能做什么」时,用接口:
java
// Bird 能飞,Airplane 也能飞
interface Flyable {
void fly();
}
class Bird implements Flyable {
@Override
public void fly() {
System.out.println("鸟儿飞翔");
}
}
class Airplane implements Flyable {
@Override
public void fly() {
System.out.println("飞机飞翔");
}
}需要多继承
当一个类需要「继承」多个不相关的行为时,用接口:
java
interface Runnable {
void run();
}
interface Swimmable {
void swim();
}
// Duck 能跑又能游
class Duck implements Runnable, Swimmable {
@Override
public void run() { }
@Override
public void swim() { }
}声明能力,不关心实现
接口只关心「能做什么」,不关心「怎么做」:
java
// 只关心:能比较
interface Comparable<T> {
int compareTo(T other);
}
// 具体怎么比,让实现类决定
class Student implements Comparable<Student> {
int score;
@Override
public int compareTo(Student other) {
return this.score - other.score;
}
}实际案例对比
用抽象类:动物系统
java
// 所有动物有共同属性和行为
abstract class Animal {
protected String name;
protected int age;
// 通用实现:睡觉
public void sleep() {
System.out.println(name + "在睡觉");
}
// 抽象方法:吃的方式不同
public abstract void eat();
}
// Dog is an Animal
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗在吃狗粮");
}
}
// Cat is an Animal
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫在吃猫粮");
}
}用接口:能力系统
java
// 可以飞
interface Flyable {
void fly();
}
// 可以游泳
interface Swimmable {
void swim();
}
// 可以被驯养
interface Domesticable {
void beDomesticated();
}
// 企鹅:不会飞,能游泳,能被驯养
class Penguin implements Swimmable, Domesticable {
@Override
public void swim() { }
@Override
public void beDomesticated() { }
}
// 鹦鹉:会飞,能被驯养
class Parrot implements Flyable, Domesticable {
@Override
public void fly() { }
@Override
public void beDomesticated() { }
}选型决策树
需要继承多个不相关的契约?
是 → 用接口
否 ↓
有明确的「是」关系吗?
是 → 用抽象类
否 ↓
需要共享代码吗?
是 → 用抽象类
否 → 用接口进阶:两者结合
实际开发中,经常会看到两者结合使用:
java
// 抽象类:定义骨架
abstract class Animal {
protected String name;
public void sleep() { } // 通用行为
public abstract void eat(); // 子类实现
}
// 接口:定义扩展能力
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
// 具体实现
class Dog extends Animal {
@Override
public void eat() { }
}
class Duck extends Animal implements Flyable, Swimmable {
@Override
public void eat() { }
@Override
public void fly() { }
@Override
public void swim() { }
}总结
抽象类 = 父亲:给你一些东西,也规定你必须做什么
接口 = 合同:我不在乎你怎么实现,只在乎你能做到| 场景 | 选择 |
|---|---|
| 建立类层次 | 抽象类 |
| 多重能力组合 | 接口 |
