Java 抽象类与接口

抽象类:在面向对象的概念中,一切皆为对象,所有的对象都是通过类来描绘的,但是相应的,并不是所有的类都是用来描绘对象的,如果一个类中没有足够的信息(变量和方法)来清晰具体的描绘对象,也就是说它是模糊的、抽象的,没法表述清楚现实的对象,而只能让子类完成抽象到具体的实现,那么这样的类称为抽象类。
接口:抽象类是从多个类中抽象出来的模板,但实际上它还不够抽象(还能有具体的方法),如果将这种抽象贯彻的更彻底,那便可以得到一种更加特殊的“抽象类”——接口(Interface)。接口是 Java 中最重要的概念之一,它可以被理解为一种特殊的类,不同的是接口的成员没有执行体,是由全局常量和公共的抽象方法所组成。
抽象类(Abstract Class)和接口(Interface)是Java实现抽象多态的两大核心机制,也是面向对象设计的基石。两者都不能被实例化,但设计理念、语法规则和使用场景有本质区别,应用非常广泛。

一、抽象类(Abstract Class)

1. 定义与语法

abstract关键字修饰的类称为抽象类,但更确切的说,如果一个类包含了抽象的方法(只包含方法的声明,而不包含方法体)则该类就必须定义成抽象类,相当于一旦有了抽象的行为(方法),就打上了抽象的标签,它是对一类事物的共性抽象,代表"是什么"(is-a)的关系。

public abstract class class_name {     //声明抽象类
    //声明抽象类成员
    }

示例:

// 抽象类定义
public abstract class Animal {
    // 成员变量
    protected String name;
    
    // 构造方法(抽象类有构造方法,供子类调用)
    public Animal(String name) {
        this.name = name;
    }
    
    // 抽象方法:只有声明,没有实现(用abstract修饰)
    public abstract void makeSound();
    
    // 非抽象方法:有具体实现,子类可直接继承或重写
    public void eat() {
        System.out.println(name + "在吃东西");
    }
}

// 子类继承抽象类
public class Cat extends Animal {
    public Cat(String name) {
        super(name); // 必须调用父类构造方法
    }
    
    // 必须实现所有抽象方法(否则子类也必须声明为abstract)
    @Override
    public void makeSound() {
        System.out.println(name + "喵喵叫");
    }
}

2. 核心特点

  • 不能实例化:只能作为父类被继承,无法通过new创建对象!(对象的产生,它一切应该有的就必须是实在的,方法必须全有而且可实现,否则不完整,但抽象类包含抽象的方法,无法实现,想像一下机器人有手有脚有轮子但不能行动,那和模型有什么区别)
  • 可以包含:成员变量、构造方法、普通方法、抽象方法、静态方法、final方法。
  • 继承规则:一个类只能继承一个抽象类(Java单继承限制)
  • 抽象方法约束:子类必须实现父类所有抽象方法,否则子类自身也必须是抽象类
  • 访问权限:抽象方法不能是privatestaticfinal(否则无法被重写)

二、接口(Interface)

1. 定义与语法

接口是对行为的抽象,代表"能做什么"(can-do)的关系,它所有的方法都是抽象的,他完全把程序的功能和实现分开来,本质是一套契约规范。虽然抽象类体现了方法重写的优势,但它的约束总归较小(还能有具体的方法),而接口的约束就很强,它强行让子类重新所有方法。而且Java的单继承结构是一个树状结构,但还是有一些局限,可以利用接口弥补这个不足,即一个子类可以实现多个接口

// 用interface关键字定义接口
interface Flyable {
    // 接口里只能有抽象方法(JDK8之前)
    // 抽象方法默认是public abstract,可以省略不写
    void fly();
}

interface Swimmable {
    void swim();
}

// 2. 类用implements实现接口
class Bird implements Flyable {
    // 必须实现接口的所有抽象方法
    @Override
    public void fly() {
        System.out.println("小鸟在天上飞");
    }
}

// 一个类可以同时实现多个接口
class Duck implements Flyable, Swimmable {
    @Override
    public void fly() {
        System.out.println("鸭子在低空飞");
    }

    @Override
    public void swim() {
        System.out.println("鸭子在水里游");
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        // Flyable f = new Flyable(); // 错误!接口不能直接new
        Flyable bird = new Bird(); // 正确!用实现类创建对象
        bird.fly(); // 输出:小鸟在天上飞

        Duck duck = new Duck();
        duck.fly(); // 输出:鸭子在低空飞
        duck.swim(); // 输出:鸭子在水里游
    }
}

2. 核心特点

  • 不能实例化:只能被类实现(implements)或被其他接口继承(extends
  • 成员限制(JDK8前):只能有public static final常量和public abstract抽象方法
  • JDK8+增强:支持默认方法(default)、静态方法(static
  • JDK9增强:支持私有方法(private)和私有静态方法
  • 多实现支持:一个类可以同时实现多个接口(解决Java单继承的局限性)
  • 继承规则:接口可以继承多个接口(interface A extends B, C

三、抽象类 vs 接口:核心区别对比

对比维度 抽象类 接口
定义关键字 abstract class interface
继承/实现方式 类用extends继承,单继承 类用implements实现,多实现;接口用extends继承,多继承
成员变量 任意访问权限,可修改 默认public static final,必须初始化,不可修改
构造方法 有构造方法(供子类调用) 无构造方法
方法类型 抽象方法、普通方法、静态方法、final方法 JDK8前:仅抽象方法
JDK8+:抽象方法、默认方法、静态方法
JDK9+:新增私有方法
访问权限 支持publicprotecteddefaultprivate 方法默认public,不能使用其他访问权限
设计理念 的抽象,提取共性代码,体现"is-a"关系 行为的抽象,定义契约规范,体现"can-do"关系
代码复用 通过继承实现,复用父类的实现代码 通过实现接口实现,复用行为规范,不能复用实现(默认方法除外)

四、使用场景与最佳实践

1. 什么时候用抽象类?

当多个类有共同的属性和方法,并且它们之间是 “是一个” 的关系时,用抽象类
例:猫、狗都是动物 → 抽象类 Animal

  • 需要在多个相关类之间共享代码(提取公共方法和属性)
  • 需要定义子类的模板行为(模板方法模式)
  • 需要为子类提供默认实现
  • 类之间存在明确的层次关系(is-a)

示例:模板方法模式

public abstract class Game {
    // 模板方法:定义游戏流程(final防止子类重写流程)
    public final void play() {
        initialize();
        startPlay();
        endPlay();
    }
    
    protected abstract void initialize();
    protected abstract void startPlay();
    protected abstract void endPlay();
}

2. 什么时候用接口?

当多个类有共同的行为,但没有共同的属性,或者需要一个类有多种行为时,用接口
例:鸟、飞机都会飞 → 接口 Flyable

  • 需要定义多个不相关类的共同行为
  • 需要实现多态解耦(依赖倒置原则)
  • 需要定义系统的外部契约(如API、回调接口)
  • 需要实现插件化架构(可插拔组件)

示例:回调接口

// 回调接口
public interface OnClickListener {
    void onClick();
}

// 按钮类
public class Button {
    private OnClickListener listener;
    
    public void setOnClickListener(OnClickListener listener) {
        this.listener = listener;
    }
    
    public void click() {
        if (listener != null) {
            listener.onClick(); // 触发回调
        }
    }
}

3. 组合使用:抽象类+接口

实际开发中,两者经常结合使用:

  • 抽象类实现接口,提供接口的默认实现(适配器模式)
  • 子类继承抽象类,同时实现其他接口

示例:适配器模式

// 接口
public interface Shape {
    void draw();
    void resize();
}

// 抽象适配器类:实现接口,提供空实现
public abstract class ShapeAdapter implements Shape {
    @Override
    public void draw() {}
    
    @Override
    public void resize() {}
}

// 子类只需重写需要的方法
public class Circle extends ShapeAdapter {
    @Override
    public void draw() {
        System.out.println("画圆形");
    }
}

五、常见误区

  1. 误区1:抽象类必须包含抽象方法
    • 纠正:抽象类可以没有抽象方法(此时主要用于禁止实例化)
  2. 误区2:接口只能有抽象方法
    • 纠正:JDK8+支持默认方法和静态方法,JDK9+支持私有方法
  3. 误区3:接口的默认方法和抽象类的普通方法没有区别
    • 纠正:默认方法不能访问实例变量,而抽象类的普通方法可以;一个类可以实现多个接口的默认方法,但只能继承一个抽象类的普通方法
  4. 误区4:抽象类和接口都不能有静态方法
    • 纠正:两者都可以有静态方法,接口的静态方法通过接口名调用,抽象类的静态方法通过类名调用

六、总结

  • 抽象类是类的抽象,用于代码复用和定义模板,体现"is-a"关系
  • 接口是行为的抽象,用于定义契约和实现多态,体现"can-do"关系
  • 优先使用接口定义行为,抽象类用于实现代码复用
  • 当需要同时复用代码和定义契约时,使用"抽象类实现接口+子类继承抽象类"的组合模式

更多推荐