java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java接口和抽象类使用

Java中接口和抽象类的异同以及具体的使用场景

作者:菜鸟恒

文章主要介绍了Java中接口(Interface)和抽象类(AbstractClass)的区别和联系,包括相同点和不同点,以及它们在实际开发中的具体使用场景,结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

在 Java 中,接口(Interface) 和 抽象类(Abstract Class) 是实现抽象编程的核心机制,二者都用于定义 “规范” 而非完整实现,但在设计理念、语法规则和使用场景上存在显著差异。下面从 异同点核心区别对比具体使用场景 三方面详细解析。

一、接口和抽象类的 “相同点”

二者的核心目标都是 抽象封装共性行为 / 特征,避免代码重复,同时约束子类实现,具体相同点如下:

  1. 都不能实例化:无法通过 new 关键字创建对象(抽象类本质是 “不完整的类”,接口是 “纯规范”,均缺少完整实现)。
  2. 都可包含抽象方法:抽象方法是 “只有声明、没有实现” 的方法(接口中默认抽象,抽象类需显式加 abstract 关键字),子类必须实现所有抽象方法(否则子类仍为抽象类)。
  3. 都用于被继承 / 实现:抽象类通过 extends 被继承,接口通过 implements 被实现,子类 / 实现类需遵循其定义的规范。
  4. 都支持多态:可以声明抽象类 / 接口类型的引用,指向其具体子类 / 实现类的对象(核心体现 “面向接口编程” 思想)。

示例(多态共性):

// 抽象类多态
abstract class Animal {}
class Dog extends Animal {}
Animal animal = new Dog(); // 合法
// 接口多态
interface Flyable {}
class Bird implements Flyable {}
Flyable flyable = new Bird(); // 合法

二、接口和抽象类的 “核心区别”

这是重点,需从 语法规则 和 设计理念 两方面区分,下表是全面对比:

对比维度抽象类(Abstract Class)接口(Interface)
继承 / 实现方式子类通过 extends 单继承(Java 不支持多继承)类通过 implements 多实现(可同时实现多个接口)
构造方法可以有构造方法(用于子类初始化时调用 super()不能有构造方法(接口无 “实例状态”,仅定义规范)
成员变量可包含任意成员变量(public/private/protected、静态 / 非静态)只能是 public static final 常量(默认隐式修饰,必须初始化)
成员方法

1. 抽象方法(abstract 修饰);

2. 普通非抽象方法(有方法体);3. JDK8+ 支持默认方法(default)和静态方法(static

1. JDK7-:只能是抽象方法(默认 public abstract);

2. JDK8+:支持默认方法(default)和静态方法(static);

3. JDK9+:支持私有方法(private,用于内部复用)

访问权限成员可声明任意权限(private/protected/public所有成员(方法、常量)默认 public(显式声明也只能是 public
设计理念体现 “is-a” 关系(子类是抽象类的 “一种具体实现”,包含继承的属性和行为)体现 “has-a” 关系(类 “具备” 接口定义的功能,是对行为的补充扩展)
核心作用封装子类的共性属性和行为(既有抽象规范,也有具体实现复用)定义纯行为规范(不关心类的本质,只约束必须实现的功能)
灵活性单继承限制,灵活性低多实现 + 接口继承(extends 多个接口),灵活性高

关键区别详解(避免踩坑)

继承限制:抽象类只能单继承(子类 extends 一个抽象类),接口支持多实现(类 implements A, B)+ 接口多继承(接口 extends A, B)。示例(接口多继承):

interface Runable { void run(); }
interface Flyable { void fly(); }
// 接口可继承多个接口,合并规范
interface SuperAbility extends Runable, Flyable {}
// 类可实现多个接口,实现所有抽象方法
class Superman implements SuperAbility {
    @Override 
    public void run() {}
    @Override 
    public void fly() {}
}

成员变量差异

方法实现差异

抽象类的普通方法可直接被子类复用:

abstract class Vehicle {
    // 具体方法(子类可直接使用,无需重写)
    public void refuel() {
        System.out.println("加油...");
    }
    // 抽象方法(子类必须实现)
    public abstract void drive();
}
class Car extends Vehicle {
    @Override 
    public void drive() { 
        System.out.println("开车..."); 
    }
}
Car car = new Car();
car.refuel(); // 直接复用父类方法,输出“加油...”

接口的默认方法(JDK8+)是为了 “接口升级不破坏原有实现类”,实现类可重写:

interface Greet {
    default void sayHello() {
        System.out.println("Hello");
    }
}
class Chinese implements Greet {
    // 可选重写默认方法
    @Override 
    public void sayHello() {
        System.out.println("你好");
    }
}

三、具体使用场景(核心:选对设计方向)

选择接口还是抽象类,核心看 你要表达的是 “继承关系” 还是 “功能扩展”,以及是否需要复用具体实现。

1. 优先使用抽象类的场景

当你需要定义一个 “类的模板”,子类和父类是 “is-a” 关系,且需要复用 属性或具体方法 时,用抽象类。典型场景:

共性属性 + 行为复用:例如 Animal 抽象类(子类 Dog/Cat 是 “一种动物”),包含 name 属性和 eat() 具体方法(所有动物都要吃饭,实现相同),同时定义 makeSound() 抽象方法(不同动物叫声不同,子类实现)。

abstract class Animal {
    protected String name;
    // 具体方法:复用实现
    public void eat() {
        System.out.println(name + "在吃饭");
    }
    // 抽象方法:约束子类实现
    public abstract void makeSound();
}
class Dog extends Animal {
    public Dog(String name) {
        this.name = name; 
    }
    @Override 
    public void makeSound() { 
        System.out.println("汪汪叫"); 
    }
}
class Cat extends Animal {
    public Cat(String name) { 
        this.name = name; 
    }
    @Override 
    public void makeSound() { 
        System.out.println("喵喵叫"); 
    }
}

2. 优先使用接口的场景

当你需要定义 “功能规范”,类和接口是 “has-a” 关系,且需要 多实现扩展 或 跨类层次复用行为 时,用接口。典型场景:

定义纯行为规范(不关心类的本质):例如 Runnable 接口(只要求类实现 run() 方法,不管是 ThreadTask 还是其他类)、Comparable 接口(只要求类实现比较逻辑)。

// 接口定义“可比较”规范
interface Comparable<T> {
    int compareTo(T o);
}
// 任意类都可实现该接口,获得比较能力(跨类层次复用)
class Student implements Comparable<Student> {
    private int score;
    @Override 
    public int compareTo(Student o) {
        return this.score - o.score;
    }
}
class Product implements Comparable<Product> {
    private double price;
    @Override 
    public int compareTo(Product o) {
        return Double.compare(this.price, o.price);
    }
}

3. 抽象类和接口结合使用(最佳实践)

实际开发中,常结合二者的优势:抽象类提供基础实现,接口定义扩展功能。典型示例:Java 集合框架中的 AbstractCollection(抽象类)和 Collection(接口)。

代码简化示例:

// 接口:定义规范
interface Collection {
    boolean add(Object obj);
    int size();
    boolean isEmpty();
}
// 抽象类:实现通用方法,减少子类重复代码
abstract class AbstractCollection implements Collection {
    private int size = 0;
    @Override public boolean add(Object obj) {
        // 子类需实现具体添加逻辑,但size递增可复用
        doAdd(obj);
        size++;
        return true;
    }
    @Override public int size() { return size; }
    @Override public boolean isEmpty() { return size == 0; }
    // 抽象方法:子类必须实现具体添加逻辑
    protected abstract void doAdd(Object obj);
}
// 具体子类:只需关注核心实现
class MyList extends AbstractCollection {
    private Object[] elements = new Object[10];
    @Override protected void doAdd(Object obj) {
        // 实现数组添加逻辑
        for (int i = 0; i < elements.length; i++) {
            if (elements[i] == null) {
                elements[i] = obj;
                break;
            }
        }
    }
}

四、总结(核心选型口诀)

  1. 若子类和父类是 “is-a” 关系,需复用属性或具体方法 → 用 抽象类
  2. 若类需要具备多种无关功能,或定义纯行为规范 → 用 接口
  3. 若需既保证基础实现复用,又支持灵活扩展 → 抽象类 + 接口 结合使用。

本质区别:抽象类是 “类的模板”,侧重继承和复用;接口是 “行为的契约”,侧重规范和扩展。遵循 “面向接口编程” 的设计思想,优先使用接口(解耦、灵活),仅在需要复用具体实现时才用抽象类。

到此这篇关于Java中接口和抽象类的异同以及具体的使用场景的文章就介绍到这了,更多相关java接口和抽象类使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文